Regras básicas de projeto

Material cedido por Jacques Saúvé (poucas alterações no original)

Objetivos da seção

    Aprender algumas das regras básicas nas quais o programador deve se apoiar ao projetar software

       Buscamos princípios de um bom projeto OO

    Acompanhar um exemplo de refatoramento de software, transformando um software de qualidade pobre em um de melhor qualidade

Regras básicas de design

    O que é Design?

       É uma das partes mais difíceis da programação

    Consiste em criar abstrações

       Isto significa três coisas:

i)    Quais classes devem ser criadas?

ii)Quais responsabilidades (métodos) devem ser assumidas por cada classe?

iii)        Quais são os relacionamentos entre tais classes e objetos dessas classes?

    Criar boas abstrações é difícil e vem com experiência

       Porém, algumas regras básicas ajudarão a adquirir a experiência mais rapidamente

Responsabilidades

    Responsabilidades são obrigações de um tipo ou de uma classe

       Obrigações de fazer algo

i)    Fazer algo a si mesmo

ii)Iniciar ações em outros objetos

iii)        Controlar ou coordenar atividades em outros objetos

       Obrigações de conhecer algo

i)    Conhecer dados encapsulados

ii)Conhecer objetos relacionados

iii)        Conhecer coisas que ele pode calcular

    Exemplos

       Uma Conta bancária tem a responsabilidade de “logar” as transações (fazer algo)

       Uma Conta bancária tem a responsabilidade de saber sua data de criação (conhecer algo)

Regra 1: Keep It Simple, Stupid (KISS)

    Lembre de Saint-Exupéry:

       “Atingimos a perfeição não quando nada pode acrescentar-se a um projeto mas quando nada pode retirar-se”

    Esta é a MegaRegra

Regra 2: Colocar as Responsabilidades com os Dados

    Qual é o princípio mais fundamental para atribuir responsabilidades?

       É este: Atribuir uma responsabilidade ao expert de informação - a classe que possui a informação necessária para preencher a responsabilidade

       Exemplo: Entre as seguintes classes do mundo bancário, (Agencia, Conta, ContaCaixa, ContaSimples, Extrato, ExtratoHTML, Moeda, Movimento, Real, Transacao), quem deve ser responsável pela responsabilidade “Localizar a conta com certo número”?

i)    Perguntamos: onde estão guardadas as Contas? (onde estão os dados)

ii)Estão na Agencia

iii)        Portanto, a classe Agencia deve ter a responsabilidade (através do método localizarConta(int número))

       Consequências

i)    A encapsulação é mantida, já que objetos usam sua própria informação para cumprir suas responsabilidades

ii)Leva a fraco acoplamento entre objetos e sistemas mais robustos e fáceis de manter

       Leva a alta coesão, já que os objetos fazem tudo que é relacionado à sua própria informação

    Também conhecido como:

       "Quem sabe, faz"

       "Expert"

       "Animação" (objetos são vivos e podem assumir qualquer responsabilidade, mesmo que sejam passivos no mundo real)

i)    Exemplo: No mundo bancário real, uma agência é algo passivo e não "localiza contas"

       "Eu mesmo faço"

       "Colocar os serviços junto aos atributos que eles manipulam"

Regra 3: Fraco Acoplamento

    O problema:

       Como minimizar dependências e, conseqüentemente, maximizar o reuso?

       O acoplamento é uma medida de quão fortemente uma classe está conectada, possui conhecimento ou depende de outra classe

       Com fraco acoplamento, uma classe não é dependente de muitas outras classes

       Com uma classe possuindo forte acoplamento, temos os seguintes problemas:

i)    Mudanças a uma classe relacionada força mudanças locais à classe

ii)A classe é mais difícil de entender isoladamente

iii)        A classe é mais difícil de ser reusada, já que depende da presença de outras classes

    A solução: Atribuir responsabilidades de forma a minimizar o acoplamento

    Discussão

       Minimizar acoplamento é um dos princípios de ouro do projeto OO

       Acoplamento de manifesta de várias formas:

i)    X tem um atributo que referencia uma instância de Y

ii)X tem um método que referencia uma instância de Y

(1)                 Pode ser parâmetro, variável local, objeto retornado pelo método

iii)        X é uma subclasse direta ou indireta de Y

iv)        X implementa a interface Y

       A herança é um tipo de acoplamento particularmente forte

       Não se deve minimizar acoplamento criando alguns poucos objetos monstruosos (God classes)

i)    Exemplo: todo o comportamento numa classe e outras classes usadas como depósitos passivos de informação

    Exemplo: Ordenação de registros de alunos por matrícula e nome

class Aluno {

    String nome;

    long   matrícula;

   

    public String getNome() { return nome; }

    public long   getMatrícula() { return matrícula; }

 

    // etc.

}

 

ListaOrdenada listaDeAlunos = new ListaOrdenada();

Aluno novoAluno = new Aluno(...);

//etc.

listaDeAlunos.add(novoAluno);

    Agora vejamos os problemas

class ListaOrdenada {

    Object[] elementosOrdenados = new Object[tamanhoAdequado];

 

    public void add(Aluno x) {

         // código não mostrado aqui

         // ...

         long matrícula1 = x.getMatrícula();

         long matrícula2 = elementosOrdenados[k].getMatrícula();

         if(matrícula1 < matrícula2) {

              // faça algo

         } else {

              // faça outra coisa

         }

    }

}

    O problema da solução anterior é que há forte acoplamento

       ListaOrdenada sabe muita coisa de Aluno

i)    O fato de que a comparação de alunos é feito com a matrícula

ii)O fato de que a matrícula é obtida com getMatrícula()

iii)        O fato de que matrículas são long (representação de dados)

iv)        Como comparar matrículas (com <)

       O que ocorre se mudarmos qualquer uma dessas coisas?

    Solução 2: mande uma mensagem para o próprio objeto se comparar com outro

class ListaOrdenada {

    Object[] elementosOrdenados = new Object[tamanhoAdequado];

 

    public void add(Aluno x) {

         // código não mostrado

         // ...

         if(x.compareTo(elementosOrdenados[K]) < 0) {

              // faça algo

         } else {

              // faça outra coisa

         }

    }

}

    Reduzimos o acoplamento escondendo informação atrás de um método

    Problema: ListaOrdenada só funciona com Aluno

    Solução 3: use interfaces para desacoplar mais ainda

interface Comparable {

    public int compareTo(Object outro);

}

 

class Aluno implements Comparable {

    public int compareTo(Object outro) {

         // compare registro de aluno com outro

         return ...

    }

}

 

class ListaOrdenada {

    Object[] elementosOrdenados = new Object[tamanhoAdequado];

 

    public void add(Comparable x) {

         // código não mostrado

         if(x.compareTo(elementosOrdenados[K]) < 0) {

              // faça algo

         } else {

              // faça outra coisa

         }

    }

}

    Outro exemplo de redução de acoplamento: polimorfismo com interfaces

    Temos vários tipos de composites (coleções) que não pertencem a uma mesma hierarquia

       ColeçãoDeAlunos

       ColeçãoDeProfessores

       ColeçãoDeDisciplinas

    Temos um cliente comum dessas coleções

       Digamos um selecionador de objetos usado numa interface gráfica para abrir uma list box para selecionar objetos com um determinado nome

    Exemplo:

       Quero listar todos os alunos com nome "João" e exibi-los numa list box para escolha pelo usuário

       Idem para listar professores com nome "Alfredo"

       Idem para listar disciplinas com nome "Programação"

       Queremos fazer um único cliente para qualquer uma das coleções

    O exemplo abaixo tem polimorfismo em dois lugares

interface SelecionávelPorNome {

    Iterator<Nomeável> getIteradorPorNome(String nome);

}

 

interface Nomeável {

    String getNome();

}

 

classe ColeçãoDeAlunos implements SelecionávelPorNome {

    // ...

    Iterator<Nomeável> getIteradorPorNome(String nome) {

        // ...

    }

}

 

classe Aluno implements Nomeável {

    // ...

    String getNome() { ... }

}

 

classe ColeçãoDeProfessores implements SelecionávelPorNome {

    // ...

    Iterator<Nomeável> getIteradorPorNome(String nome) {

        // ...

    }

}

 

classe Professor implements Nomeável {

    // ...

    String getNome() { ... }

}

 

classe ColeçãoDeDisciplinas implements SelecionávelPorNome {

    // ...

    Iterator<Nomeável> getIteradorPorNome(String nome) {

        // ...

    }

}

 

classe Disciplina implements Nomeável {

    // ...

    String getNome() { ... }

}

 

classe ComponenteDeSeleção {

    Iterator<Nomeável> it;

    // observe o tipo do parâmetro (uma interface)

    public ComponenteDeSeleção(SelecionávelPorNome coleção, String nome) {

        it = coleção.getIteradorPorNome(nome); // chamada polimórfica

    }

    // ...

    void geraListBox() {

        response.out.println("<select name=\"nome\" size=\"1\">");

        while(it.hasNext()) {

                int i = 1;

                // observe o tipo do objeto

                Nomeável obj = it.next();

                response.out.println("<option value=\"escolha" + i + "\">" +

                                      obj.getNome() + // chamada polimórfica

                                      "</option>");

        }

        response.out.println("</select>");

    }

}

 

    // Como usar o código acima num servlet:

    // supõe que as coleções usam o padrão Singleton

    ComponenteDeSeleção cds = new ComponenteDeSeleção(

                                    ColeçãoDeAlunos.getInstance(), "João");

    cds.geraListBox();

 

    cds = new ComponenteDeSeleção(ColeçãoDeDisciplinas.getInstance(), "Programação");

    cds.geraListBox();

Regra 4: Alta Coesão

    O problema:

       Como gerenciar a complexidade?

       A coesão mede quão relacionados ou focados estão as responsabilidades da classe

i)    Também chamado coesão funcional

       Uma classe com baixa coesão faz muitas coisas não relacionadas e leva aos seguintes problemas:

i)    Difícil de entender

ii)Difícil de reusar

iii)        Difícil de manter

iv)        É “delicada”: constantemente sendo afetada por outras mudanças

       Uma classe com baixa coesão assume responsabilidades que pertencem a outras classes e deveriam ser delegadas a outras

    Solução:

       Atribuir responsabilidades que mantenham alta coesão

    Uma classe deve ter um pequeno número de variáveis e métodos que manipulam essas variáveis

       Coesão máxima ocorre quando todas as variáveis são manipuladas por todos os métodos da classe

       Quando a coesão é alta dizemos que os métodos e variáveis da classe são co-dependentes e formam uma unidade lógica

    Olhar coesão pode ajudar a quebrar classes

       Quebrar um método grande em vários menores pode gerar novas classes

       Quando descobrimos que eles manipulam conjuntos disjuntos de variáveis

       Geralmente descobrimos que a classe tem responsabilidades demais

    Exemplo: O que acha da classe que segue?

class Angu {

    List números;

    String texto;

 

    public static int acharPadrão(String padrão) {

        // ... lida com texto aqui

    }

    public static int média() {

        // ... lida com números aqui

    }

    public static outputStream abreArquivo(string nomeArquivo) {

        // ...

    }

}

 

class Xpto extends Angu { // quer aproveitar código de Angu

    ...

}

    Classes com alta coesão são preferíveis: mais robustas, mais confiáveis mais fáceis de serem reusadas e entendidas;

       A coesão baixa resulta em código difícil de manter, de testar, de reusar e de entender!

Princípio da responsabilidade única

       Classes devem ser pequenas. Quanto é pequeno?

       Mede-se o tamanho de uma classe pela quantidade de responsabilidades que elas têm

i)    Deve ser possível descrever a classe rapidamente com umas 25 palavras

ii)Dar um nome à classe deve ser fácil   

(1)                 O nome descreve suas responsabilidades

(2)                 O nome ajuda a determinar o tamanho de classe

(3)                 Se estiver difícil nomear a classe, desconfie de que existe um problema!

iii)        O ideal é que uma classe deve ter apenas uma razão para mudar

(1)                 Classe que compila e imprime relatórios

(2)                 Pode mudar por diferentes razões

    Esta classe deve ser quebrada em pelo menos 2 outras!

Se você escreve classes pequenas, coesas e que se relacionam com poucas outras classes também pequenas e coesas; e se estas classes não dependerem fortemente umas das outras, então você está no caminho certo.

Um Exemplo de Refatoramento

    Como programador, você precisa treinar seu nariz para detectar “mau cheiro” em código

    Veremos um exemplo disso agora

    Melhoraremos um programa através de refatoramento

       Refatoramento altera um programa mas sem afetar a funcionalidade que ele oferece

    Refatoramento sempre deve ser feito apoiando-se em Testes de Unidade para assegurar-se de que as transformações no código não quebrem código que funciona

       Não mostraremos os testes de unidade aqui por questão de tempo

O Programa Original

    Referência: Livro de Fowler: “Refactoring”

    O programa é simples: ele calcula e emite um extrato de um cliente numa locadora de vídeo

    Vamos executar o programa para ver uma saída possível:

Registro de Alugueis de Guilherme Lopes

   Batman                         14.0

   George, o curioso             1.5

   O espetacular homem aranha    90.0

   Power Rangers Mistic Force    12.0

   Charlie e Lola                12.0

   Uma noite no museu            3.5

Valor total devido: 133.0

Voce acumulou 8 pontos de alugador frequente

    Eis o diagrama UML das classes principais

                 

    A classe DVD é usada apenas para conter os atributos

package p2.exemplos.locadora;

 

public class DVD {

  public static final int NORMAL = 0;

  public static final int LANÇAMENTO = 1;

  public static final int INFANTIL = 2;

 

  private String título;

  private int códigoDePreço;

 

  public DVD(String título, int códigoDePreço) {

    this.título = título;

    this.códigoDePreço = códigoDePreço;

  }

 

  public String getTítulo() {

    return título;

  }

 

  public int getCódigoDePreço() {

    return códigoDePreço;

  }

 

  public void setCódigoDePreço(int códigoDePreço) {

    this.códigoDePreço = códigoDePreço;

  }

}

    A classe Aluguel representa o aluguel de um DVD por um certo número de dias

package p2.exemplos.locadora;

 

public class Aluguel {

   private DVD dvd;

   private int diasAlugado;

 

   public Aluguel(DVD dvd, int diasAlugado) {

      this.dvd = dvd;

      this.diasAlugado = diasAlugado;

   }

 

   public DVD getDVD() {

      return dvd;

   }

 

   public int getDiasAlugado() {

      return diasAlugado;

   }

}

    A classe Cliente representa um freguês da locadora de vídeo

package p2.exemplos.locadora;

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

 

public class Cliente {

  private String nome;

  private List<Aluguel> dvdsAlugados = new ArrayList<Aluguel>();

 

  public Cliente(String nome) {

    this.nome = nome;

  }

 

  public String getNome() {

    return nome;

  }

 

  public void adicionaAluguel(Aluguel aluguel) {

    dvdsAlugados.add(aluguel);

  }

 

  public String extrato() {

    final String fimDeLinha = System.getProperty("line.separator");

    double valorTotal = 0.0;

    int pontosDeAlugadorFrequente = 0;

    Iterator<Aluguel> alugueis = dvdsAlugados.iterator();

    String resultado = "Registro de Alugueis de " + getNome() + fimDeLinha;

    while(alugueis.hasNext()) {

      double valorCorrente = 0.0;

      Aluguel cada = alugueis.next();

 

      // determina valores para cada linha

      switch(cada.getDVD().getCódigoDePreço()) {

      case DVD.NORMAL:

        valorCorrente += 2;

        if(cada.getDiasAlugado() > 2) {

          valorCorrente += (cada.getDiasAlugado() - 2) * 1.5;

        }

        break;

      case DVD.LANÇAMENTO:

        valorCorrente += cada.getDiasAlugado() * 3;

        break;

      case DVD.INFANTIL:

        valorCorrente += 1.5;

        if(cada.getDiasAlugado() > 3) {

          valorCorrente += (cada.getDiasAlugado() - 3) * 1.5;

        }

        break;

      } //switch

      // trata de pontos de alugador frequente

      pontosDeAlugadorFrequente++;

      // adiciona bonus para aluguel de um lançamento por pelo menos 2 dias

      if(cada.getDVD().getCódigoDePreço() == DVD.LANÇAMENTO &&

         cada.getDiasAlugado() > 1) {

         pontosDeAlugadorFrequente++;

      }

 

      // mostra valores para este aluguel

      resultado += "\t" + cada.getDVD().getTítulo() + "\t" + valorCorrente + fimDeLinha;

      valorTotal += valorCorrente;

    } // while

    // adiciona rodapé

    resultado += "Valor total devido: " + valorTotal + fimDeLinha;

    resultado += "Voce acumulou " + pontosDeAlugadorFrequente +

              " pontos de alugador frequente";

    return resultado;

  }

}

    Finalmente, a classe Locadora exercita o programa

package p2.exemplos.locadora;

 

public class Locadora {

   public static void main(String[] args) {

      Cliente c1 = new Cliente("Guilherme Lopes");

 

      c1.adicionaAluguel(new Aluguel(new DVD(

            "Batman                        ", DVD.NORMAL), 10));

      c1.adicionaAluguel(new Aluguel(new DVD(

            "George, o curioso             ", DVD.INFANTIL), 2));

      c1.adicionaAluguel(new Aluguel(new DVD(

            "O espetacular homem aranha    ", DVD.LANÇAMENTO), 30));

      c1.adicionaAluguel(new Aluguel(new DVD(

            "Power Rangers Mistic Force    ", DVD.LANÇAMENTO), 4));

      c1.adicionaAluguel(new Aluguel(new DVD(

            "Charlie e Lola                ", DVD.INFANTIL), 10));

      c1.adicionaAluguel(new Aluguel(new DVD(

            "Uma noite no museu            ", DVD.NORMAL), 3));

 

      System.out.println(c1.extrato());

   }

}

    Nosso exemplo é pequeno devido a restrições de tempo, mas imagine o que ocorreria se um código grande fosse tão mal feito quanto o que veremos agora

                                              

Comentários sobre o Programa Original

    O programa não apresenta um bom projeto “orientado a objeto”

    O mau cheiro que indica isso é:

       O método extrato() é muito grande e faz tudo sozinho

       Não há responsabilidades assumidas pelas classes DVD e Aluguel

    Mas o que importa isso se o programa funciona?

       Código ruim é difícil de alterar/manter!

i)    Se é difícil, então bugs serão introduzidos mais facilmente

       Exemplo: o que deve ser mudado para ter um extrato em HTML?

i)    Nada pode ser reusado!

ii)Um novo método inteiro deve ser escrito, sem aproveitar código existente

       Claro que você pode resolver isso com “copy-and-paste

i)    Mas o que ocorre se as regras de preços mudarem?

ii)Vai ter que alterar código em dois lugares

       Outro exemplo: a classificação em 3 tipos de DVDs vai mudar mas os donos da locadora não sabem exatamente o que querem ainda e você pode ter certeza que haverá várias mudanças ao longo do tempo

i)    Nosso código está pronto para lidar facilmente com um novo esquema de classificação de DVDs? Não.

       Nosso código está pronto para lidar facilmente com um novo esquema de pontos de alugador frequente? Não.

Refatoramento: Decomposição e Redistribuição do método extrato()

    Antes de continuar, repetimos: Para refatorar, você precisa ter testes automáticos

       Vamos supor que eles existam (não os veremos por questão de tempo)

    Ataquemos o primeiro problema: o método extrato() é muito grande e “faz tudo sozinho”

       Vamos decompor este método em pedaços menores

    Vamos pegar um bloco de código com alguma coesão e vamos extrair e colocá-lo num método

       Qual bloco escolher?

       A experiência é importante aqui mas também lembre a regra sobre Alta Coesão

       O switch é o cálculo de valorCorrente para um DVD e parece um pedaço coeso que merece um método à parte

i)    Teste de coesão: O trabalho que o método faz pode ser dito numa frase curta?

ii)Se puder, é um bom método a ser criado

iii)        Aqui, podemos dizer que o bloco extraído “calcula o preço de aluguel de um DVD” parece coeso

    Segue o código antes e depois do refatoramento, com o sublinha indicando as mudanças

    Antes

package p2.exemplos.locadora;

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

 

public class Cliente {

  private String nome;

  private List<Aluguel> dvdsAlugados = new ArrayList<Aluguel>();

 

  public Cliente(String nome) {

    this.nome = nome;

  }

 

  public String getNome() {

    return nome;

  }

 

  public void adicionaAluguel(Aluguel aluguel) {

    dvdsAlugados.add(aluguel);

  }

 

  public String extrato() {

    final String fimDeLinha = System.getProperty("line.separator");

    double valorTotal = 0.0;

    int pontosDeAlugadorFrequente = 0;

    Iterator<Aluguel> alugueis = dvdsAlugados.iterator();

    String resultado = "Registro de Alugueis de " + getNome() + fimDeLinha;

    while(alugueis.hasNext()) {

      double valorCorrente = 0.0;

      Aluguel cada = alugueis.next();

 

      // determina valores para cada aluguel

      switch(cada.getDVD().getCódigoDePreço()) {

      case DVD.NORMAL:

        valorCorrente += 2;

        if(cada.getDiasAlugado() > 2) {

          valorCorrente += (cada.getDiasAlugado() - 2) * 1.5;

        }

        break;

      case DVD.LANÇAMENTO:

        valorCorrente += cada.getDiasAlugado() * 3;

        break;

      case DVD.INFANTIL:

        valorCorrente += 1.5;

        if(cada.getDiasAlugado() > 3) {

          valorCorrente += (cada.getDiasAlugado() - 3) * 1.5;

        }

        break;

      } //switch

 

      // trata de pontos de alugador frequente

      pontosDeAlugadorFrequente++;

      // adiciona bonus para aluguel de um lançamento por pelo menos 2 dias

      if(cada.getDVD().getCódigoDePreço() == DVD.LANÇAMENTO &&

         cada.getDiasAlugado() > 1) {

         pontosDeAlugadorFrequente++;

      }

 

      // mostra valores para este aluguel

      resultado += "\t" + cada.getDVD().getTítulo() + "\t" + valorCorrente + fimDeLinha;

      valorTotal += valorCorrente;

    } // while

    // adiciona rodapé

    resultado += "Valor total devido: " + valorTotal + fimDeLinha;

    resultado += "Voce acumulou " + pontosDeAlugadorFrequente +

              " pontos de alugador frequente";

    return resultado;

  }

}

    Depois

package p2.exemplos.locadora;

 

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

 

public class Cliente {

   private String nome;

   private List<Aluguel> dvdsAlugados = new ArrayList<Aluguel>();

 

   public Cliente(String nome) {

      this.nome = nome;

   }

 

   public String getNome() {

      return nome;

   }

 

   public void adicionaAluguel(Aluguel aluguel) {

      dvdsAlugados.add(aluguel);

   }

 

   public String extrato() {

      final String fimDeLinha = System.getProperty("line.separator");

      double valorTotal = 0.0;

      int pontosDeAlugadorFrequente = 0;

      Iterator<Aluguel> alugueis = dvdsAlugados.iterator();

      String resultado = "Registro de Alugueis de " + getNome() + fimDeLinha;

      while (alugueis.hasNext()) {

         double valorCorrente = 0.0;

         Aluguel cada = alugueis.next();

 

         valorCorrente = valorDeUmAluguel(cada);

 

         // trata de pontos de alugador frequente

         pontosDeAlugadorFrequente++;

         // adiciona bonus para aluguel de um lançamento por pelo menos 2

         // dias

         if (cada.getDVD().getCódigoDePreço() == DVD.LANÇAMENTO

               && cada.getDiasAlugado() > 1) {

            pontosDeAlugadorFrequente++;

         }

 

         // mostra valores para este aluguel

         resultado += "\t" + cada.getDVD().getTítulo() + "\t"

               + valorCorrente + fimDeLinha;

         valorTotal += valorCorrente;

      } // while

      // adiciona rodapé

      resultado += "Valor total devido: " + valorTotal + fimDeLinha;

      resultado += "Voce acumulou " + pontosDeAlugadorFrequente

            + " pontos de alugador frequente";

      return resultado;

   }

 

   /*

    * novo metodo extraido de extrato!

    */

   private int valorDeUmAluguel(Aluguel cada) {

      int valorCorrente = 0;

      // determina valores para cada linha

      switch (cada.getDVD().getCódigoDePreço()) {

      case DVD.NORMAL:

         valorCorrente += 2;

         if (cada.getDiasAlugado() > 2) {

            valorCorrente += (cada.getDiasAlugado() - 2) * 1.5;

         }

         break;

      case DVD.LANÇAMENTO:

         valorCorrente += cada.getDiasAlugado() * 3;

         break;

      case DVD.INFANTIL:

         valorCorrente += 1.5;

         if (cada.getDiasAlugado() > 3) {

            valorCorrente += (cada.getDiasAlugado() - 3) * 1.5;

         }

         break;

      } // switch

      return valorCorrente;

   }

}

    Depois de uma mudança dessas, compilamos e testamos para verificar que não quebramos nada

       Ao testar, verificamos que vários testes falham!

       Examinando os testes que falharam e o código, observamos logo que usamos “int” em vez de “double”

    Daí a importância de sempre ter testes para refatorar

    O método é mudado para a versão seguinte:

   private double valorDeUmAluguel(Aluguel cada) {

      double valorCorrente = 0;

      // determina valores para cada linha

      switch (cada.getDVD().getCódigoDePreço()) {

      case DVD.NORMAL:

         valorCorrente += 2;

         if (cada.getDiasAlugado() > 2) {

            valorCorrente += (cada.getDiasAlugado() - 2) * 1.5;

         }

         break;

      case DVD.LANÇAMENTO:

         valorCorrente += cada.getDiasAlugado() * 3;

         break;

      case DVD.INFANTIL:

         valorCorrente += 1.5;

         if (cada.getDiasAlugado() > 3) {

            valorCorrente += (cada.getDiasAlugado() - 3) * 1.5;

         }

         break;

      } // switch

      return valorCorrente;

   }

    Separamos o método grande em dois

       Agora podemos continuar a trabalhar em cada pedaço individualmente

       Princípio da Divisão-e-Conquista para lidar com a complexidade

    Vamos fazer uma mudança pequena no método valorDeAluguel

       Algumas variáveis vão mudar de nome

   private double valorDeUmAluguel(Aluguel umAluguel) {

      double valorDoAluguel = 0;

      // determina valores para cada linha

      switch (umAluguel.getDVD().getCódigoDePreço()) {

      case DVD.NORMAL:

         valorDoAluguel += 2;

         if (umAluguel.getDiasAlugado() > 2) {

            valorDoAluguel += (umAluguel.getDiasAlugado() - 2) * 1.5;

         }

         break;

      case DVD.LANÇAMENTO:

         valorDoAluguel += umAluguel.getDiasAlugado() * 3;

         break;

      case DVD.INFANTIL:

         valorDoAluguel += 1.5;

         if (umAluguel.getDiasAlugado() > 3) {

            valorDoAluguel += (umAluguel.getDiasAlugado() - 3) * 1.5;

         }

         break;

      } // switch

      return valorDoAluguel;

   }

    Vale a pena mudar nomes de variáveis assim?

       Claro!

       O código deve comunicar bem seu propósito para outros programadores

       Nomes de variáveis são um meio básico de comunicação

MCj04280830000[1]

Qualquer pessoa pode escrever código que um computador entende. Bons programadores escrevem código que um ser humano pode entender.

 

Refatoramento: Movendo o Cálculo de Valores

    Examine o código do método valorDeUmAluguel()

       O método usa informação de um objeto da classe Aluguel, mas nada usa do Cliente

       Pela regra de design “Colocar as Responsabilidades com os Dados”, desconfiamos que o método está na classe errada

    Vamos mover o método para a classe Aluguel

    O novo código de Aluguel segue

package p2.exemplos.locadora;

 

public class Aluguel {

   private DVD dvd;

   private int diasAlugado;

 

   public Aluguel(DVD dvd, int diasAlugado) {

      this.dvd = dvd;

      this.diasAlugado = diasAlugado;

   }

 

   public DVD getDVD() {

      return dvd;

   }

 

   public int getDiasAlugado() {

      return diasAlugado;

   }

  

   /*

    * novo metodo extraido de extrato!

    */

   public double valorDeUmAluguel() {

      double valorDoAluguel = 0;

      // determina valores para cada linha

      switch (getDVD().getCódigoDePreço()) {

      case DVD.NORMAL:

         valorDoAluguel += 2;

         if (getDiasAlugado() > 2) {

            valorDoAluguel += (getDiasAlugado() - 2) * 1.5;

         }

         break;

      case DVD.LANÇAMENTO:

         valorDoAluguel += getDiasAlugado() * 3;

         break;

      case DVD.INFANTIL:

         valorDoAluguel += 1.5;

         if (getDiasAlugado() > 3) {

            valorDoAluguel += (getDiasAlugado() - 3) * 1.5;

         }

         break;

      } // switch

      return valorDoAluguel;

   }

}

    Observe que o parâmetro Aluguel umAluguel sumiu

    Na prática, temos um passo intermediário em que delegamos Cliente.valorDeUmAluguel para Aluguel.valorDeUmAluguel

       Fazemos isso para fazer pequenas mudanças de cada vez ao refatorar

       Depois de testar, podemos remover o método valorDeUmAluguel e chamar getValorDoAluguel() diretamente

    Código antes

public class Cliente {

   .

   .

   .

   public String extrato() {

      .

      .

      .

      while (alugueis.hasNext()) {

         Aluguel cada = alugueis.next();

         double valorCorrente = valorDeUmAluguel(cada);

      .

      .

      .

    Código depois

public class Cliente {

   .

   .

   .

   public String extrato() {

      .

      .

      .

      while (alugueis.hasNext()) {

         Aluguel cada = alugueis.next();

         double valorCorrente = cada.valorDeUmAluguel();

      .

      .

      .

    Isso parece muito mais orientado a objeto!

       A classe correta (Aluguel) assumiu a responsabilidade de calcular o preço do aluguel

    O próximo passo pode ser a remoção de variáveis temporárias desnecessárias

       Exemplo: valorCorrente em extrato()

       Variáveis a menos são coisas a menos para dar errado, não ter valor correto, não ser inicializada, etc.

    Código antes

   public String extrato() {

      final String fimDeLinha = System.getProperty("line.separator");

      double valorTotal = 0.0;

      int pontosDeAlugadorFrequente = 0;

      Iterator<Aluguel> alugueis = dvdsAlugados.iterator();

      String resultado = "Registro de Alugueis de " + getNome() + fimDeLinha;

      while (alugueis.hasNext()) {

         Aluguel cada = alugueis.next();

 

         double valorCorrente = cada.valorDeUmAluguel();

 

         // trata de pontos de alugador frequente

         pontosDeAlugadorFrequente++;

         // adiciona bonus para aluguel de um lançamento por pelo menos 2

         // dias

         if (cada.getDVD().getCódigoDePreço() == DVD.LANÇAMENTO

               && cada.getDiasAlugado() > 1) {

            pontosDeAlugadorFrequente++;

         }

 

         // mostra valores para este aluguel

         resultado += "\t" + cada.getDVD().getTítulo() + "\t"

               + valorCorrente + fimDeLinha;

         valorTotal += valorCorrente;

      } // while

      // adiciona rodapé

      resultado += "Valor total devido: " + valorTotal + fimDeLinha;

      resultado += "Voce acumulou " + pontosDeAlugadorFrequente

            + " pontos de alugador frequente";

      return resultado;

   }

    Código depois

   public String extrato() {

      final String fimDeLinha = System.getProperty("line.separator");

      double valorTotal = 0.0;

      int pontosDeAlugadorFrequente = 0;

      Iterator<Aluguel> alugueis = dvdsAlugados.iterator();

      String resultado = "Registro de Alugueis de " + getNome() + fimDeLinha;

      while (alugueis.hasNext()) {

         Aluguel cada = alugueis.next();

 

         // trata de pontos de alugador frequente

         pontosDeAlugadorFrequente++;

         // adiciona bonus para aluguel de um lançamento por pelo menos 2

         // dias

         if (cada.getDVD().getCódigoDePreço() == DVD.LANÇAMENTO

               && cada.getDiasAlugado() > 1) {

            pontosDeAlugadorFrequente++;

         }

 

         // mostra valores para este aluguel

         resultado += "\t" + cada.getDVD().getTítulo() + "\t"

               + cada.valorDeUmAluguel() + fimDeLinha;

         valorTotal += cada.valorDeUmAluguel();

      } // while

      // adiciona rodapé

      resultado += "Valor total devido: " + valorTotal + fimDeLinha;

      resultado += "Voce acumulou " + pontosDeAlugadorFrequente

            + " pontos de alugador frequente";

      return resultado;

   }

    Claro que depois de cada mudança, compile e teste

Refatoramento: Extração de Pontos de Alugador Frequente

    O que fizemos com o valor do aluguel pode ser feito com o cálculo dos pontos de alugador frequente (PAF)

    Quem deve ter a responsabilidade de calcular os PAF?

       O cálculo depende de informação que Aluguel conhece

       Deixe portanto o cálculo na classe Aluguel

    Código antes:

   public String extrato() {

      final String fimDeLinha = System.getProperty("line.separator");

      double valorTotal = 0.0;

      int pontosDeAlugadorFrequente = 0;

      Iterator<Aluguel> alugueis = dvdsAlugados.iterator();

      String resultado = "Registro de Alugueis de " + getNome() + fimDeLinha;

      while (alugueis.hasNext()) {

         Aluguel cada = alugueis.next();

 

         // trata de pontos de alugador frequente

         pontosDeAlugadorFrequente++;

         // adiciona bonus para aluguel de um lançamento por pelo menos 2

         // dias

         if (cada.getDVD().getCódigoDePreço() == DVD.LANÇAMENTO

               && cada.getDiasAlugado() > 1) {

            pontosDeAlugadorFrequente++;

         }

 

         // mostra valores para este aluguel

         resultado += "\t" + cada.getDVD().getTítulo() + "\t"

               + cada.valorDeUmAluguel() + fimDeLinha;

         valorTotal += cada.valorDeUmAluguel();

      } // while

      // adiciona rodapé

      resultado += "Valor total devido: " + valorTotal + fimDeLinha;

      resultado += "Voce acumulou " + pontosDeAlugadorFrequente

            + " pontos de alugador frequente";

      return resultado;

   }

    Código depois

package p2.exemplos.locadora;

 

public class Aluguel {

   private static final int PONTO_EXTRA = 2;

   private static final int PONTO_SIMPLES = 1;

  

   .

   .

   .

 

   public int getPontosDeAlugadorFrequente() {

      if (getDVD().getCódigoDePreço() == DVD.LANÇAMENTO

            && getDiasAlugado() > 1) {

         return PONTO_EXTRA;

      }

      return PONTO_SIMPLES;

   }

}

 

 

public class Cliente {

   .

   .

   .

   public String extrato() {

      final String fimDeLinha = System.getProperty("line.separator");

      double valorTotal = 0.0;

      int pontosDeAlugadorFrequente = 0;

      Iterator<Aluguel> alugueis = dvdsAlugados.iterator();

      String resultado = "Registro de Alugueis de " + getNome() + fimDeLinha;

      while (alugueis.hasNext()) {

         Aluguel cada = alugueis.next();

 

         pontosDeAlugadorFrequente+=cada.getPontosDeAlugadorFrequente();

 

         // mostra valores para este aluguel

         resultado += "\t" + cada.getDVD().getTítulo() + "\t"

               + cada.valorDeUmAluguel() + fimDeLinha;

         valorTotal += cada.valorDeUmAluguel();

      } // while

      // adiciona rodapé

      resultado += "Valor total devido: " + valorTotal + fimDeLinha;

      resultado += "Voce acumulou " + pontosDeAlugadorFrequente

            + " pontos de alugador frequente";

      return resultado;

   }

   .

   .

   .

}

Refatoramento: Remoção de Variáveis Temporárias

    Mais uma vez, vamos falar de variáveis temporárias

    Embora elas possam ser úteis, elas freqüentemente são indicativos de “mau cheiro”

    Examine, por exemplo, a variável valorTotal

       Ela é usada para calcular o valor total do extrato enquanto estamos no loop

       Na realidade, o loop está servindo para três coisas:

i)    Montar o String do extrato

ii)Calcular o valor total

iii)        Calcular os PAF

       Porém, esse trabalho talvez seja necessário em outro lugar

i)    Por exemplo, posso querer saber o valor total em outro método (extratoHTML()) e terei portanto que repetir o cálculo do preço total neste lugar

       Faz sentido criarmos um método getValorTotal()?

i)    Este método faz algo que podemos resumir em uma frase curta?

ii)Sim! Portanto, crie o método

    Foi o mesmo que aconteceu com a variável temporária pontosDeAlugadorFrequente

       Foi melhor criar um método getPontosDeAlugadorFrequente()

    Embora esses dois passos devam ser feitos separadamente com testes a cada passo, vamos logo ver o resultado dos dois passos

    Código antes:

   public String extrato() {

      final String fimDeLinha = System.getProperty("line.separator");

      double valorTotal = 0.0;

      int pontosDeAlugadorFrequente = 0;

      Iterator<Aluguel> alugueis = dvdsAlugados.iterator();

      String resultado = "Registro de Alugueis de " + getNome() + fimDeLinha;

      while (alugueis.hasNext()) {

         Aluguel cada = alugueis.next();

 

         pontosDeAlugadorFrequente+=cada.getPontosDeAlugadorFrequente();

 

         // mostra valores para este aluguel

         resultado += "\t" + cada.getDVD().getTítulo() + "\t"

               + cada.valorDeUmAluguel() + fimDeLinha;

         valorTotal += cada.valorDeUmAluguel();

      } // while

      // adiciona rodapé

      resultado += "Valor total devido: " + valorTotal + fimDeLinha;

      resultado += "Voce acumulou " + pontosDeAlugadorFrequente

            + " pontos de alugador frequente";

      return resultado;

   }

    Código depois

   public String extrato() {

      final String fimDeLinha = System.getProperty("line.separator");

      Iterator<Aluguel> alugueis = dvdsAlugados.iterator();

      String resultado = "Registro de Alugueis de " + getNome() + fimDeLinha;

      while (alugueis.hasNext()) {

         Aluguel cada = alugueis.next();

 

         // mostra valores para este aluguel

         resultado += "\t" + cada.getDVD().getTítulo() + "\t"

               + cada.valorDeUmAluguel() + fimDeLinha;

      } // while

      // adiciona rodapé

      resultado += "Valor total devido: " + getValorTotal() + fimDeLinha;

      resultado += "Voce acumulou " + getPontosTotaisDeAlugadorFrequente()

            + " pontos de alugador frequente";

      return resultado;

   }

  

   private double getValorTotal() {

       double valorTotal = 0.0;

       Iterator<Aluguel> alugueis = dvdsAlugados.iterator();

       while(alugueis.hasNext()) {

         Aluguel cada = (Aluguel)alugueis.next();

         valorTotal += cada.valorDeUmAluguel();

       }

       return valorTotal;   

     }

 

     private int getPontosTotaisDeAlugadorFrequente() {

       int pontos = 0;

       Iterator<Aluguel> alugueis = dvdsAlugados.iterator();

       while(alugueis.hasNext()) {

         Aluguel cada = (Aluguel)alugueis.next();

         pontos += cada.getPontosDeAlugadorFrequente();

       }

       return pontos;

     }

    Ooops!!! Pare aí! Acabamos de deixar o código maior com a última mudança!

    Valeu a pena? Sim!

       Motivos:

i)    Criamos dois métodos úteis que poderão ser usados mais na frente

ii)Eles poderão até ser tornados públicos se for necessário que entrem na interface da classe

iii)        Organizamos o código melhor onde cada pedacinho é mais simples de entender

(1)                 Compare o método original extrato() com a última versão

       E quanto ao desempenho?? Temos mais loops do que antes!

i)    É possível que haja um problema de desempenho mas só saberemos isso com um perfil de execução

ii)Neste caso, numa aplicação de locadora de vídeo onde clientes alugam poucos DVDs, eu garanto que o desempenho não será afetado

(1)                 Os loops adicionais vão adicionar alguns milissegundos ao processamento

(2)                 Mas quanto tempo demora para imprimir o extrato em papel?!!?

    Agora podemos ver como é fácil criar um extrato em HTML devido à existência dos dois métodos úteis que criamos

package p2.exemplos.locadora;

 

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

 

public class Cliente {

   .

   .

   .

   public String extratoHTML() {

      Iterator<Aluguel> alugueis = dvdsAlugados.iterator();

       String resultado = "<H1>Registro de Alugueis de <EM>" +

                          getNome() + "</EM></H1><P>\n";

       while(alugueis.hasNext()) {

         Aluguel cada = alugueis.next();

 

         // mostra valores para este aluguel

         resultado += cada.getDVD().getTítulo() + ": " +

                      cada.valorDeUmAluguel() + "<BR>\n";

       } // while

       // adiciona rodapé

       resultado += "<P>Valor total devido: <EM>" + getValorTotal() + "</EM>\n";

       resultado += "<P>Voce acumulou <EM>" + getPontosTotaisDeAlugadorFrequente() +

                 "</EM> pontos de alugador frequente";

       return resultado;

     }

}

Refatoramento: Responsabilidades onde estão os dados

    O switch está com problemas

       Ao ver um switch, verifique se o teste está sendo feito em cima dos seus próprios dados ou em cima dos dados de outro objeto

       Aqui, vemos que o Aluguel faz um switch em cima de dados do DVD!

       Portanto, faz mais sentido mover o método getValorDeUmAluguel() para a classe DVD

    Código antes:

package p2.exemplos.locadora;

 

public class Aluguel {

   .

   .

   .

   public double valorDeUmAluguel() {

      double valorDoAluguel = 0;

      // determina valores para cada linha

      switch (getDVD().getCódigoDePreço()) {

      case DVD.NORMAL:

         valorDoAluguel += 2;

         if (getDiasAlugado() > 2) {

            valorDoAluguel += (getDiasAlugado() - 2) * 1.5;

         }

         break;

      case DVD.LANÇAMENTO:

         valorDoAluguel += getDiasAlugado() * 3;

         break;

      case DVD.INFANTIL:

         valorDoAluguel += 1.5;

         if (getDiasAlugado() > 3) {

            valorDoAluguel += (getDiasAlugado() - 3) * 1.5;

         }

         break;

      } // switch

      return valorDoAluguel;

   }

   .

   .

   .

}

    Código depois

package p2.exemplos.locadora;

 

public class DVD {

      .

      .

      .

 

      /*

    * novo metodo extraido de extrato!

    */

   public double valorDeUmAluguel(int diasAlugado) {

      double valorDoAluguel = 0;

      // determina valores para cada linha

      switch (getCódigoDePreço()) {

      case NORMAL:

         valorDoAluguel += 2;

         if (diasAlugado > 2) {

            valorDoAluguel += (diasAlugado - 2) * 1.5;

         }

         break;

      case DVD.LANÇAMENTO:

         valorDoAluguel += diasAlugado * 3;

         break;

      case DVD.INFANTIL:

         valorDoAluguel += 1.5;

         if (diasAlugado > 3) {

            valorDoAluguel += (diasAlugado - 3) * 1.5;

         }

         break;

      } // switch

      return valorDoAluguel;

   }

}

 

package p2.exemplos.locadora;

 

public class Aluguel {

   .

   .

   . 

   public double valorDeUmAluguel() {

      return dvd.valorDeUmAluguel(diasAlugado);

   }

}

    Podemos fazer o mesmo com os pontos de alugador freqüente

    Código antes:

package p2.exemplos.locadora;

 

public class Aluguel {

  

   .

   .

   .

   private static final int PONTO_EXTRA = 2;

   private static final int PONTO_SIMPLES = 1;

  

   public int getPontosDeAlugadorFrequente() {

      if (getDVD().getCódigoDePreço() == DVD.LANÇAMENTO

            && getDiasAlugado() > 1) {

         return PONTO_EXTRA;

      }

      return PONTO_SIMPLES;

   }

}

    Código depois

package p2.exemplos.locadora;

 

public class DVD {

   .

   .

   .

   private static final int PONTO_EXTRA = 2;

   private static final int PONTO_SIMPLES = 1;

  

   public int getPontosDeAlugadorFrequente(int diasAlugado) {

      if (getCódigoDePreço() == DVD.LANÇAMENTO

            && diasAlugado > 1) {

         return PONTO_EXTRA;

      }

      return PONTO_SIMPLES;

   }

}

 

package p2.exemplos.locadora;

 

public class Aluguel {

   .

   .

   .

   public double getPontosDeAlugadorFrequente () {

      return dvd.getPontosDeAlugadorFrequente(diasAlugado);

   }

}

Refatoramento: Uso de Polimorfismo

Refatoramento: Interfaces

    Temos uma classe (DVD) que possui dois métodos que têm comportamento diferente dependendo de algum atributo do objeto

       Veja o switch de valorDeUmAluguel()

       Veja o teste em getPontosDeAlugadorFrequente()

    Isso é indicativo que o polimorfismo poderia limpar as coisas

    De fato, DVDs diferentes poderiam responder de forma diferente às duas perguntas valorDeUmAluguel() e getPontosDeAlugadorFrequente()

    Podemos portanto ter polimorfismo em cima de tipos de DVDs

    Melhor ainda: queremos isolar dois mundos

       O mundo das coisas que podem ser alugadas (DVDs, jogos, CDs, ...)

       O mundo que usa tais coisas

    Usaremos uma interface para isolar esses dois mundos

    Vamos primeiro definir uma interface para a situação

       Chamaremos a interface de Alugavel

       Agora, serão DVDs, mas depois poderão ser Blu-ray, jogos, etc.

    Código antes:

package p2.exemplos.locadora;

 

public class Aluguel {

 

   private DVD dvd;

   private int diasAlugado;

 

   public Aluguel(DVD dvd, int diasAlugado) {

      this.dvd = dvd;

      this.diasAlugado = diasAlugado;

   }

 

   public DVD getDVD() {

      return dvd;

   }

 

   public int getDiasAlugado() {

      return diasAlugado;

   }

 

   public int getPontosDeAlugadorFrequente() {

      return dvd.getPontosDeAlugadorFrequente(diasAlugado);

   }

  

   public double valorDeUmAluguel() {

      return dvd.valorDeUmAluguel(diasAlugado);

   }

}

    Código depois

package p2.exemplos.locadora;

 

public interface Alugavel {

  public String getTítulo();

  public double getValorDoAluguel(int diasAlugada);

  public int getPontosDeAlugadorFrequente(int diasAlugada);

}

 

 

package p2.exemplos.locadora;

 

public class DVD implements Alugavel {

.

.

.

}

 

 

package p2.exemplos.locadora;

 

public class Aluguel {

 

   private Alugavel item;

   private int diasAlugado;

 

   public Aluguel(Alugavel item, int diasAlugado) {

      this.item = item;

      this.diasAlugado = diasAlugado;

   }

 

   public Alugavel getItem() {

      return item;

   }

 

   public int getDiasAlugado() {

      return diasAlugado;

   }

 

   public int getPontosDeAlugadorFrequente() {

      return item.getPontosDeAlugadorFrequente(diasAlugado);

   }

  

   public double getValorDoAluguel() {

      return item.getValorDoAluguel(diasAlugado);

   }

}

Refatoramento: Herança

    Ainda não temos polimorfismo porque apenas uma classe implementa a interface Alugavel

    A primeira solução que vem à mente é fazer como segue:

    Mas isso não funciona porque um DVD pode mudar sua classificação durante sua vida

       Não é bonito mudar a classe de um objeto importante durante sua vida

    Como lidar com isso?

       Separe o que é igual daquilo que muda

       Encapsule cada um em objetos diferentes

    Resultado:

    Observe que cada DVD agora será composto de dois objetos:

       Um para o DVD em si

       Um para a classificação do DVD

    Falamos que está havendo composição de objetos

    Para implementar getValorDoAluguel(), o DVD delega para o objeto de classificação

       Por que tudo isso é melhor?

i)    A composição pode ser alterada de forma simples e elegante em tempo de execução

ii)Isto é, o DVD recebe um novo objeto composto de Classificacao

iii)        Isso faz com que a composição seja freqüentemente superior à herança

iv)        A herança ainda ocorre, mas não no mundo dos DVDs, mas no mundo das classificações. Algo simples, bem definido, coeso e em um lugar bem definido do programa

       Agora, vamos fazer isso acontecer no código

       São 3 passos:

i)    Implementar a composição de objetos de forma a permitir a mudança dinâmica do objeto de classificação

ii)Mover o método getValorDoAluguel de DVD para Classificacao

iii)        Substituir os testes (switch/if) com polimorfismo

    Façamos a primeira etapa

    Queremos que cada DVD vire dois objetos: um DVD associado a uma classificação

       Por enquanto, o objeto Classificacao é quem vai responder getCódigoDePreço()

       Está havendo delegação

i)    Não quero que a interface externa mude para quem usa a classe DVD

ii)Portanto, quem cria o novo objeto é a própria classe DVD para esconder tudo

    Código depois:

package p2.exemplos.locadora;

 

public class DVD implements Alugavel {

   public static final int NORMAL = 0;

   public static final int LANÇAMENTO = 1;

   public static final int INFANTIL = 2;

 

   private String título;

   private Classificacao classificação;

 

   public DVD(String título, int códigoDePreço) {

      this.título = título;

      setCódigoDePreço(códigoDePreço);

   }

 

   public void setCódigoDePreço(int códigoDePreço) {

      switch (códigoDePreço) {

      case NORMAL:

         classificação = new ClassificacaoNormal();

         break;

      case LANÇAMENTO:

         classificação = new ClassificacaoLancamento();

         break;

      case INFANTIL:

         classificação = new ClassificacaoInfantil();

         break;

      }

   }

 

   public String getTítulo() {

      return título;

   }

 

   public int getCódigoDePreço() {

      return classificação.getCódigoDePreço();

   }

 

   /*

    * novo metodo extraido de extrato!

    */

   public double getValorDoAluguel(int diasAlugado) {

      return classificação.getValorDoAluguel(diasAlugado);

   }

 

   public int getPontosDeAlugadorFrequente(int diasAlugado) {

      return classificação.getPontosDeAlugadorFrequente(diasAlugado);

   }

}

 

package p2.exemplos.locadora;

 

public abstract class Classificacao {

  abstract int getCódigoDePreço();

  abstract double getValorDoAluguel(int diasAlugada);

 

  int getPontosDeAlugadorFrequente(int diasAlugadas) {

    return 1;

  }

}

 

package p2.exemplos.locadora;

 

class ClassificacaoInfantil extends Classificacao {

   int getCódigoDePreço() {

      return DVD.INFANTIL;

   }

 

   double getValorDoAluguel(int diasAlugado) {

      double valorDoAluguel = 1.5;

      if (diasAlugado > 3) {

         valorDoAluguel += (diasAlugado - 3) * 1.5;

      }

      return valorDoAluguel;

   }

}

 

package p2.exemplos.locadora;

 

class ClassificacaoNormal extends Classificacao {

   int getCódigoDePreço() {

      return DVD.NORMAL;

   }

 

   double getValorDoAluguel(int diasAlugado) {

      double valorDoAluguel = 2;

      if (diasAlugado > 2) {

         valorDoAluguel += (diasAlugado - 2) * 1.5;

      }

      return valorDoAluguel;

   }

}

 

package p2.exemplos.locadora;

 

class ClassificacaoLancamento extends Classificacao {

   int getCódigoDePreço() {

      return DVD.LANÇAMENTO;

   }

 

   double getValorDoAluguel(int diasAlugado) {

      return diasAlugado * 3;

   }

 

   int getPontosDeAlugadorFrequente(int diasAlugados) {

      return (diasAlugados > 1) ? 2 : 1;

   }

}

    Observe que, em tempo de execução, podemos mudar a classificação de uma fita

       Basta chamar setCódigoDePreço(), como antes

    Os objetos de classificação devem implementar getValorDoAluguel() e getPontosDeAlugadorFrequente() que estão em DVD

       Mais uma vez, teremos delegação

       O objeto DVD delega para Classificacao o cálculo de getValorDoAluguel() e getPontosDeAlugadorFrequente()

       Estes métodos são chamados polimorficamente

    Valeu a pena tanto esforço para introduzir polimorfismo?

       Primeiramente, um bom programador já teria colocado polimorfismo desde o início

       Segundo, valeu a pena sim: o código é muito mais simples de mudar quando houver um novo esquema de preços, por exemplo.

Palavras Finais

    Uma faceta importante do refactoring é o ritmo

       Testa, mude um pouco, testa, mude um pouco, ...

       Este ritmo permite fazer refatoramento de forma simples, rápida e segura

 

Voltar