Composição, Herança, etc.

Objetivos da seção

    Apresentar o conceito de reuso de classes

    Apresentar o reuso por composição (solidificar o conceito)

    Apresentar o conceito de reuso por herança

    Como representar herança em UML

    Comparar reuso por composição e reuso por herança

    Upcasting e downcasting

Reuso de classes

Qual é o problema?

    Fazer software é difícil

    Fazer software de qualidade é lento e caro

    Não temos tecnologia ainda para fazer software grande do zero, rapidamente e com poucos bugs

Qual é a solução?

    O reuso é “o” caminho mais freqüentemente apontado

       As mesmas idéias básicas devem ter sido projetadas e reprojetadas pela mesma pessoa ou pessoas diferentes muitas vezes, com certeza!

       Será que temos que começar tudo de novo, sempre???

Por que a solução não é fácil?

    Reuso não acontece automaticamente!

    Para reusar, tem que:

       Bolar boas abstrações (i.e., abstrações úteis); e

       Empacotá-las para facilitar o reuso

    Como reutilizar bem tem sido meio misterioso ao longo dos anos

       Como fazer isso?

       Que técnicas usar?

    Orientação a Objeto oferece o meio e prometeu muito, mas no geral o reuso ainda não é uma realidade fácil de ser alcançada

       Porque não é fácil

i)    Pouca gente conseguiu bons resultados

ii)Ficou mais no marketing (com exceção de alguns bons programadores)

    Por que reusar é difícil?

       Sugestões de William Opdyke em “Refactoring, Reuse, and Reality”:

i)    Técnicos podem não entender o que reusar ou como reusá-lo;

ii)Técnicos podem não se motivar a aplicar técnicas de reuso a não ser que benefícios de curto prazo sejam alcançáveis (porém, o reuso é mais um investimento em longo prazo);

iii)        Overhead, curva de aprendizado e custos de descobrimento devem ser endereçados para que o caminho do reuso seja bem sucedido.

Tipos de reuso

    Falaremos das técnicas historicamente empregadas para atingir o reuso

       Do mais simples/antigo para mais sofisticado/novo

       A granularidade de reuso mudou radicalmente ao longo dos anos

       Vamos parar neste curso em uma certa granularidade…

Fase pré OO: 1960-1970

Fase da revolução OO: 1980

    O que é reutilizado: conceitos inteiros através de classes

       Reusa-se como representar os dados internamente e também que operações podem ser realizadas sobre esses dados

       Nome da técnica: herança, composição/delegação

       Permite fazer Programming-by-Difference

    O que é reutilizado: Contratos de relacionamento entre objetos

       Nome da técnica: Interfaces

       Interface como conceito de “Plug Point”

       Interface como conceito de “barreira de desacoplamento”

i)    Uma lei fundamental da programação: “Program to an Interface, not to an Implementation

 

    Existe muito mais sobre reuso a ser dito, mas para a finalidade deste curso, paramos por aqui

       Moral da história: reusar é difícil, mas fundamental!

    Neste módulo do curso estaremos falando apenas de reuso de implementação

    Em um módulo seguinte falaremos mais profundamente sobre as interfaces

Composição

    Já fizemos reuso por composição várias vezes na nossa disciplina

       Quem pode dar exemplos?

    Delegar responsabilidades a objetos de outras classes

       Tipicamente, temos objetos que são atributos de uma classe

i)    Por exemplo, um objeto da classe Pessoa era o titular de uma conta

(1)                 Reusamos todo o código da classe Pessoa, atribuindo responsabilidades a objetos desta classe

(2)                 Por exemplo, no método getNomeTitular() de ContaSimples há uma chamada ao método getNome() de Pessoa

ii)A nossa classe Baralho é um agregado de cartas (objetos da classe Carta)

    Todo objeto precisa ser criado com new antes de ser usado

    Um cuidado ao fazer reuso por composição é certificar-se de que o objeto da classe a ser reusada foi corretamente inicializado

       Quando um objeto é acessado sem que tenha sido antes criado (com new), então uma exceção de tempo de execução é lançada na cara de alguém!! A chamada NullPointerException

       Falaremos mais adiante neste curso de detalhes mais sofisticados do tratamento de erros em Java

    Os objetos atributos podem ser inicializados em diferentes momentos

       No construtor da classe que está reusando outra classe por composição

       No momento em que eles são definidos

       Imediatamente antes de usá-los (lazy initialization)

       Usando inicialização de instância

    Vejamos um exemplo:

package p2.exemplos;

 

 

public class ComposicaoEInicializacao {

   private static final Object CONST = new Object();

  

   private String campo1 = "Campo1 - apressadinho ";

   private String campo2;

   private Integer campo3;

   private String campo4;

   private int campo5;

   private float campo6;

   private double campo7;

 

   public ComposicaoEInicializacao() {

      System.out.println("Entrou no construtor");

      System.out.println("campo1 = " + campo1);

      System.out.println("campo2 = " + campo2);

      System.out.println("campo3 = " + campo3);

      System.out.println("campo4 = " + campo4);

      System.out.println("campo5 = " + campo5);

      System.out.println("campo6 = " + campo6);

      System.out.println("campo7 = " + campo7);

 

      campo2 = "O correto? Nem sempre... Depende.";

      System.out.println("campo2 iniciado no construtor = " + campo2);

   }

 

// Inicialização de instância

// Instance initilization

   {

      campo3 = new Integer(3);

      System.out.println("campo3 = " + campo3);

   }

 

   @Override

   public String toString() {

      if (campo4 == null) {

         campo4 = "Campo4, atrasadinho e preguiçoso. "

                  + " Mas sempre chega a tempo ";

         System.out.println("campo4 = " + campo4);

      }

      return "toString de ComposicaoEInicializacao";

   }

  

   public void printConst() {

      System.out.println(CONST);

   }

 

   public static void main(String[] args) {

     

      System.out.println(ComposicaoEInicializacao.CONST);

     

      ComposicaoEInicializacao ci1 = new ComposicaoEInicializacao();

      System.out.println("Chamando toString() pela primeira vez:");

      System.out.println(ci1); //isso chama ci1.toString()

      System.out.println("E se chamarmos o toString de novo?");

      System.out.println(ci1); //isso chama ci1.toString() de novo

      ci1.printConst();

     

      System.out.print("\n-----------------------------------------\n");

     

      ComposicaoEInicializacao ci2 = new ComposicaoEInicializacao();

      System.out.println(ci2);

      ci2.printConst();

   }

}

    A saída deste programa é como segue:

java.lang.Object@187aeca

campo3 = 3

Entrou no construtor

campo1 = Campo1 - apressadinho

campo2 = null

campo3 = 3

campo4 = null

campo5 = 0

campo6 = 0.0

campo7 = 0.0

campo2 iniciado no construtor = O correto? Nem sempre... Depende.

Chamando toString() pela primeira vez:

campo4 = Campo4, atrasadinho e preguiçoso.  Mas sempre chega a tempo

toString de ComposicaoEInicializacao

E se chamarmos o toString de novo?

toString de ComposicaoEInicializacao

java.lang.Object@187aeca

 

-----------------------------------------

campo3 = 3

Entrou no construtor

campo1 = Campo1 - apressadinho

campo2 = null

campo3 = 3

campo4 = null

campo5 = 0

campo6 = 0.0

campo7 = 0.0

campo2 iniciado no construtor = O correto? Nem sempre... Depende.

campo4 = Campo4, atrasadinho e preguiçoso.  Mas sempre chega a tempo

toString de ComposicaoEInicializacao

java.lang.Object@187aeca

Herança

    Antes de falar sobre herança, vamos ver um exemplo

    Suponha que estamos construindo um software pra gerenciar os funcionários de uma software house

       Pessoal do serviço administrativo, programadores e gerentes de projeto

       O pessoal da limpeza e vigilância é terceirizado, não tem cadastro como funcionário da empresa

       Que abstrações devem ser elaboradas? Em outras palavras, que classes teremos?

package p2.exemplos;

 

/**

 * Representa um funcionário qualquer, que é uma pessoa que tem uma

 * matrícula, um salario e um tempo de serviço.

 *

 * @author Raquel Lopes

 *

 */

public class FuncionarioAdministrativo {

   private String nome;

   private String cpf;

   private String matricula;

   private double salarioBase;

   private int tempoDeServico;

   private Funcao funcao;

 

   public enum Funcao {

     

      OFFICE_BOY(1), SECRETARIA(3), TELEFONISTA(3), DONO(10);

     

      private int valor;

      Funcao(int valor) {

         this.valor = valor;

      }

      public int getValor() {

         return valor;

      }

     

   }

 

   /**

    * Cria um funcionário.

    *

    * @param nome

    *            O nome do funcionário.

    * @param cpf

    *            O CPF do funcionário.

    * @param matricula

    *            A matrícula do funcionário.

    * @param tempoDeServico

    *            O tempo de serviço (em meses) do funcionário.

    */

   public FuncionarioAdministrativo(String nome, String cpf,

                                    String matricula, int tempoDeServico,

                                    Funcao funcao) {

      this.nome = nome;

      this.cpf = cpf;

      this.matricula = matricula;

      this.tempoDeServico = tempoDeServico;

      this.funcao = funcao;

   }

 

   /**

    * Recupera o nome do funcionário.

    *

    * @return O nome da pessoa.

    */

   public String getNome() {

      return nome;

   }

 

   /**

    * Recupera o CPF do funcionário.

    *

    * @return O CPF associado à pessoa.

    */

   public String getCPF() {

      return cpf;

   }

 

   /**

    * Recupera a função do funcionário.

    *

    * @return A função atual do funcionário.

    */

   public Funcao getFuncao() {

      return funcao;

   }

 

   /**

    * Seta uma nova função atual para o funcionário.

    *

    * @param funcao

    *            Nova função do funcionário.

    */

   public void setFuncao(Funcao funcao) {

      this.funcao = funcao;

   }

 

   /**

    * Ajusta o nome do funcionário.

    *

    * @param nome

    *            O nome da pessoa.

    */

   public void setNome(String nome) {

      this.nome = nome;

   }

 

   /**

    * Ajusta o CPF da pessoa.

    *

    * @param cpf

    *            O CPF associado à pessoa.

    */

   public void setCPF(String cpf) {

      this.cpf = cpf;

   }

 

   /**

    * Recupera a matrícula do funcionário.

    *

    * @return A matrícula do funcionário.

    */

   public String getMatricula() {

      return matricula;

   }

 

   /**

    * Atribui uma nova matrícula ao funcionário.

    *

    * @param matricula

    *            O valor da nova matrícula.

    */

   public void setMatricula(String matricula) {

      this.matricula = matricula;

   }

 

   /**

    * Recupera o salário base do funcionário.

    *

    * @return O salário do funcionário.

    */

   public double getSalarioBase() {

      return salarioBase;

   }

 

   /**

    * Atribui um novo salário base ao funcionário.

    *

    * @param salario

    *            O novo salário do funcionário.

    */

   public void setSalarioBase(double salario) {

      salarioBase = salario;

   }

 

   /**

    * Recupera o tempo de serviço em meses do funcionário.

    *

    * @return O tempo de serviço do funcionário.

    */

   public int getTempoDeServico() {

      return tempoDeServico;

   }

 

   /**

    * Atribui um novo tempo de serviço ao funcionário que deve ser maior

    * que o tempo de serviço anterior.

    *

    * @param tempoDeServico

    *            Novo valor para tempo de serviço.

    */

   public void setTempoDeServico(int tempoDeServico) {

      if (tempoDeServico > this.tempoDeServico)

         this.tempoDeServico = tempoDeServico;

   }

  

   /**

    * Este método computa o salário do funcionário.

    *

    * @return O salário do funcionário;

    */

   public double computaSalario() {

      return salarioBase + gratificacaoPorTempoDeServico();

   }

 

   private double gratificacaoPorTempoDeServico() {

      //double gratificacaoBase = 1.24 * ((funcao.ordinal())+1);

      double gratificacaoBase = 1.24 * funcao.getValor();

      return getTempoDeServico() * gratificacaoBase;

   }

 

   /**

    * Representa um funcionário como String.

    *

    * @return A string que representa um funcionário.

    */

   public String toString() {

      return "Nome " + getNome() + ", cpf " + getCPF() + ", matricula "

            + getMatricula();

   }

 

   /**

    * Testa a igualdade de um objeto com este funcionário. Dois objetos da

    * classe FuncionarioAdministrativo são iguais se eles são a mesma

    * pessoa e têm a mesma matrícula.

    *

    * @param objeto

    *            O objeto a comparar com este funcionario.

    * @return true se o objeto for igual a este funcionario, false caso

    *         contrário.

    */

   public boolean equals(Object objeto) {

      if( objeto == null || objeto.getClass() != this.getClass() ) {

         return false;

      }

  

      FuncionarioAdministrativo func = (FuncionarioAdministrativo) objeto;

 

      return getNome().equals(func.getNome())

            && getCPF().equals(func.getCPF())

            && getMatricula().equals(func.getMatricula())

            && getTempoDeServico() == func.getTempoDeServico()

            && getFuncao().equals(func.getFuncao());

   }

 

   public static void main(String[] args) {

      FuncionarioAdministrativo f1 = new FuncionarioAdministrativo("nome", "cpf", "matricula", 1, Funcao.DONO);

      FuncionarioAdministrativo f2 = new FuncionarioAdministrativo("nome", "cpf", "matricula", 1, Funcao.DONO);

      if( f1.equals(f2) ) {

         System.out.println("iguais!");

      }

   }

}

 

package p2.exemplos.feio;

 

import java.util.ArrayList;

import java.util.List;

 

import p2.exemplos.Projeto;

 

public class Programador {

   private String nome;

   private String cpf;

   private String matricula;

   private double salarioBase;

   private int tempoDeServico;

   private List<String> linguagensEmQuePrograma;

   private String linguagemDePreferencia;

   private Projeto projetoAtual;

 

   public Programador(String nome, String cpf, String matricula,

         int tempoDeServico, String preferencia) {

      this.nome = nome;

      this.cpf = cpf;

      this.matricula = matricula;

      this.tempoDeServico = tempoDeServico;

      linguagensEmQuePrograma = new ArrayList<String>();

      linguagemDePreferencia = preferencia;

   }

 

   /**

    * Recupera o nome do funcionário.

    *

    * @return O nome da pessoa.

    */

   public String getNome() {

      return nome;

   }

 

   /**

    * Recupera o CPF do funcionário.

    *

    * @return O CPF associado à pessoa.

    */

   public String getCPF() {

      return cpf;

   }

 

   /**

    * Ajusta o nome do funcionário.

    *

    * @param nome

    *            O nome da pessoa.

    */

   public void setNome(String nome) {

      this.nome = nome;

   }

 

   /**

    * Ajusta o CPF da pessoa.

    *

    * @param cpf

    *            O CPF associado à pessoa.

    */

   public void setCPF(String cpf) {

      this.cpf = cpf;

   }

 

   /**

    * Recupera a matrícula do funcionário.

    *

    * @return A matrícula do funcionário.

    */

   public String getMatricula() {

      return matricula;

   }

 

   /**

    * Atribui uma nova matrícula ao funcionário.

    *

    * @param matricula

    *            O valor da nova matrícula.

    */

   public void setMatricula(String matricula) {

      this.matricula = matricula;

   }

 

   /**

    * Recupera o salário base do funcionário.

    *

    * @return O salário do funcionário.

    */

   public double getSalarioBase() {

      return salarioBase;

   }

 

   /**

    * Atribui um novo salário base ao funcionário.

    *

    * @param salario

    *            O novo salário do funcionário.

    */

   public void setSalarioBase(double salario) {

      salarioBase = salario;

   }

 

   /**

    * Recupera o tempo de serviço em meses do funcionário.

    *

    * @return O tempo de serviço do funcionário.

    */

   public int getTempoDeServico() {

      return tempoDeServico;

   }

 

   /**

    * Atribui um novo tempo de serviço ao programador que deve ser maior

    * que o tempo de serviço anterior.

    *

    * @param tempoDeServico

    *            Novo valor para tempo de serviço.

    */

   public void setTempoDeServico(int tempoDeServico) {

      if (tempoDeServico > this.tempoDeServico)

         this.tempoDeServico = tempoDeServico;

   }

 

   /**

    * Adiciona uma nova linguagem de programação conhecida pelo

    * programador.

    * @param lp

    *            A nova linguagem de programação que o programador conhece.

    */

   public void adicionaLinguagemConhecida(String lp) {

      if (!linguagensEmQuePrograma.contains(lp))

         linguagensEmQuePrograma.add(lp);

   }

 

   /**

    * Indica a participação do programador em um projeto.

    *

    * @param projeto

    *            O projeto em que o programador está inserido.

    */

   public void atribuiProjeto(Projeto projeto) {

      projetoAtual = projeto;

   }

 

   /**

    * Este método computa o salário do programador.

    *

    * @return O salário do funcionário;

    */

   public double computaSalario() {

      return getSalarioBase() * 1.5;

   }

 

   /**

    * @return the linguagemDePreferencia

    */

   public String getLinguagemDePreferencia() {

      return linguagemDePreferencia;

   }

 

   /**

    * @param linguagemDePreferencia

    *            the linguagemDePreferencia to set

    */

   public void setLinguagemDePreferencia(String linguagemDePreferencia) {

      this.linguagemDePreferencia = linguagemDePreferencia;

   }

 

   /**

    * Retorna a representação deste objeto em String.

    *

    * @return A string que representa este objeto.

    */

   public String toString() {

      return "Nome " + getNome() + ", cpf " + getCPF() + ", matricula "

            + getMatricula() + ", projeto " + projetoAtual.getTitulo();

   }

 

      /**

    * Testa a igualdade de um objeto com este programador. Dois objetos da

    * classe Programador são iguais se eles são a mesma pessoa e têm a

    * mesma matrícula.

    *

    * @param objeto

    *            O objeto a comparar com este programador.

    * @return true se o objeto for igual a este programador, false caso

    *         contrário.

    */

   public boolean equals(Object objeto) {

      if (!(objeto instanceof Programador)) {

         return false;

 

      }

      Programador func = (Programador) objeto;

 

      return getNome().equals(func.getNome())

            && getCPF().equals(func.getCPF())

            && getMatricula().equals(func.getMatricula());

   }

}

 

package p2.exemplos.feio;

 

import java.util.ArrayList;

import java.util.List;

 

import p2.exemplos.feio.Projeto;

 

public class Coordenador {

   private String nome;

   private String cpf;

   private String matricula;

   private double salarioBase;

   private int tempoDeServico;

   private List<Projeto> projetosQueGerencia;

 

   public Coordenador(String nome, String cpf, String matricula,

         int tempoDeServico) {

      this.nome = nome;

      this.cpf = cpf;

      this.matricula = matricula;

      this.tempoDeServico = tempoDeServico;

      projetosQueGerencia = new ArrayList<Projeto>();

   }

 

   /**

    * Recupera o nome do funcionário.

    *

    * @return O nome da pessoa.

    */

   public String getNome() {

      return nome;

   }

 

   /**

    * Recupera o CPF do funcionário.

    *

    * @return O CPF associado à pessoa.

    */

   public String getCPF() {

      return cpf;

   }

 

   /**

    * Ajusta o nome do funcionário.

    *

    * @param nome

    *            O nome da pessoa.

    */

   public void setNome(String nome) {

      this.nome = nome;

   }

 

   /**

    * Ajusta o CPF da pessoa.

    *

    * @param cpf

    *            O CPF associado à pessoa.

    */

   public void setCPF(String cpf) {

      this.cpf = cpf;

   }

 

   /**

    * Recupera a matrícula do funcionário.

    *

    * @return A matrícula do funcionário.

    */

   public String getMatricula() {

      return matricula;

   }

 

   /**

    * Atribui uma nova matrícula ao funcionário.

    *

    * @param matricula

    *            O valor da nova matrícula.

    */

   public void setMatricula(String matricula) {

      this.matricula = matricula;

   }

 

   /**

    * Recupera o salário base do funcionário.

    *

    * @return O salário do funcionário.

    */

   public double getSalarioBase() {

      return salarioBase;

   }

 

   /**

    * Atribui um novo salário base ao funcionário.

    *

    * @param salario

    *            O novo salário do funcionário.

    */

   public void setSalarioBase(double salario) {

      salarioBase = salario;

   }

 

   /**

    * Recupera o tempo de serviço em meses do funcionário.

    *

    * @return O tempo de serviço do funcionário.

    */

   public int getTempoDeServico() {

      return tempoDeServico;

   }

 

   /**

    * Atribui um novo tempo de serviço ao funcionário que deve ser maior

    * que o tempo de serviço anterior.

    *

    * @param tempoDeServico

    *            Novo valor para tempo de serviço.

    */

   public void setTempoDeServico(int tempoDeServico) {

      if (tempoDeServico > this.tempoDeServico)

         this.tempoDeServico = tempoDeServico;

   }

 

   /**

    * Este método computa o salário do coordenador.

    *

    * @return O salário do coordenador;

    */

   public double computaSalario() {

      return getSalarioBase() * 2.2 + adicionalPorTempoDeServico();

   }

 

   private double adicionalPorTempoDeServico() {

      return ((double) getTempoDeServico()) * 2.5;

   }

 

   /**

    * Faz este coordenador ser gerente de mais um projeto.

    *

    * @param proj

    *            O novo projeto que este coordenador irá gerenciar.

    * @return TRUE caso o novo projeto nao esteja ainda na lista de

    *         projetos e seja adicionado com sucesso; FALSE, caso

    *         contrário.

    */

   public boolean adicionaProjeto(Projeto proj) {

      if (projetosQueGerencia.contains(proj))

         return false;

      return projetosQueGerencia.add(proj);

   }

 

   /**

    * Faz este coordenador deixar de ser gerente de um projeto.

    *

    * @param proj

    *            O projeto que este coordenador não irá mais gerenciar.

    * @return TRUE caso o projeto seja removido com sucesso; FALSE, caso

    *         contrário.

    */

   public boolean removeProjeto(Projeto proj) {

      return projetosQueGerencia.remove(proj);

   }

 

   /**

    * Retorna a representação deste objeto em String.

    *

    * @return A string que representa este objeto.

    */

   public String toString() {

      return "Nome " + getNome() + ", cpf " + getCPF() + ", matricula "

            + getMatricula() + ", projetos que gerencia "

            + projetosQueGerencia;

   }

 

   /**

    * Testa a igualdade de um objeto com este coordenador. Dois objetos da

    * classe Coordenador são iguais se eles são a mesma pessoa e têm a

    * mesma matrícula.

    *

    * @param objeto

    *            O objeto a comparar com este coordenador.

    * @return true se o objeto for igual a este coordenador, false caso

    *         contrário.

    */

   public boolean equals(Object objeto) {

      if( objeto == null || objeto.getClass() != this.getClass() ) {

         return false;

      }

     

      Coordenador coord = (Coordenador) objeto;

 

      return getNome().equals(coord.getNome())

            && getCPF().equals(coord.getCPF())

            && getMatricula().equals(coord.getMatricula())

            && getTempoDeServico() == coord.getTempoDeServico();

      }

 

}

 

    Suponha que agora queremos também manter o estado civil e a quantidade de filhos de todos eles

    E então, como está o cheiro desse programa?

    Nas classes de funcionários da empresa de software vistas (FuncionarioAdministrativo, Programador, Coordenador), há um mau cheiro terrível no código

       Há muita repetição de código

       Isso dificulta a manutenção de software pois uma mudança pode implicar alterações em várias partes do código, o que é "prato cheio" para introduzir bugs

       Suponha que agora queremos mudar a forma como a matricula é representada internamente para todos os funcionários

    A atividade de limpar código que apresenta mau cheiro chama-se refatoramento

    Vamos refatorar as três classes de mensagens eletrônicas

    Mas antes, vamos aprender um pouco sobre herança…

Algumas palavras sobre herança

    Tipos diferentes de objetos podem ter algo comum entre si. Por exemplo:

       Bicicleta genérica e bicicleta mountain bike, caloi, cecir…

       Mensagem (genérica) e mensagem de áudio, mensagem de vídeo, mensagem multimídia, etc.

       Avião (genérico) e avião supersônico, avião caça, avião monomotor…

       Pessoa e programador, funcionário, dono de banco, etc.

    Classes podem herdar os campos (atributos) e métodos de outras classes

    A classe que deriva (ou herda) de outra classe é chamada de subclasse, ou classe filha ou classe derivada

    A classe da qual a subclasse é derivada é chamada de superclasse, ou classe base, ou classe mãe

    Qual é a idéia?

       Quando queremos criar uma nova classe e já existe uma classe que já possui parte do código que queremos, nós podemos derivar a nova classe a partir da classe já existente

       Ao fazer isso, todos os campos e métodos da classe que já existia são herdados na sua nova classe sem que você precise fazer copy&paste e melhor:

i)    Considerando que a superclasse já foi testada adequadamente, você não precisa testar o código que está sendo reusado!

ii)Mas você só pode usar diretamente, como se tivesse definido na nova classe, os campos e métodos que tenham visibilidade protected, package ou public

       Construtores não são métodos nem campos de classe, então não são herdados L. Mas existe um jeito simples de chamar o construtor da superclasse (veremos isso mais adiante!)

    A idéia é simples, intuitiva e poderosa. Faz parte de toda linguagem orientada a objetos

       Assim, existe herança em Java também! Seguem informações específicas de Java

       Em Java, toda classe que não tem uma superclasse definida explicitamente (como todas que criamos até aqui) é uma subclasse da classe Object (raiz da hierarquia de classes Java)

       Em Java já usamos herança sempre que criamos uma classe, pois todas as classes herdam implicitamente e automaticamente o comportamento da classe Object

i)    Classes podem ser derivadas de classes, que derivam de outras classes e assim sucessivamente, e no ponto mais alto da hierarquia estará sempre a classe Object

       Toda classe, exceto Object, tem uma e apenas uma superclasse. Isto é chamado de herança simples, em oposição a herança múltipla. A maioria das linguagens orientadas a objetos permite apenas herança simples porque é mais simples de implementar

i)    E se dois métodos de classes mãe tiverem a mesma assinatura? Como proceder?

    A sintaxe para herança deve deixar claro que a nova classe é como a classe “mãe”

       Em Java, usamos a palavra reservada extends seguida do nome da classe base ou classe mãe

       Todos os campos e métodos são automaticamente herdados da classe base

Voltando para nosso refatoramento: fatorando o que há de comum

    Tudo que vimos até aqui nos dá uma idéia de como refatorar nosso código?

    Primeiro juntamos tudo que tem de comum entre as três classes e criamos uma nova classe que chamaremos Pessoa

    Em seguida criamos uma outra classe, Funcionario, que deve ter tudo que a classe Pessoa tem, e mais alguns atributos e métodos

    Todos os funcionários da software house são funcionários, mas cada funcionário de fato tem sua especialização

    Vamos implementar isso usando herança

    A seguinte hierarquia representa o que acabamos de falar

 

 

 

 

 

 

 


    Funcionário herda tudo de Pessoa e acrescenta novos atributos e comportamento (métodos) a uma Pessoa, transformando-a em um Funcionario

    Programador herda tudo de Funcionario (que havia herdado tudo de Pessoa), acrescentando atributos e métodos específicos de um Programador

    O mesmo raciocínio segue para as demais classes (Coordenador e FuncionarioAdministrativo)

    Vamos implementar isso em Java?

    O resultado segue abaixo:

package p2.exemplos;

 

 

/**

 * Classe representando uma pessoa física.

 *

 * @author   Jacques Philippe Sauvé, jacques@dsc.ufpb.br

 * @version 1.1

 * <br>

 * Copyright (C) 1999 Universidade Federal da Paraíba.

 */

 

public class Pessoa  {

  

  private String nome;

  private String cpf;

 

  // Construtores

  /**

   * Constroi uma pessoa com nome e CPF dados.

   * @param nome O nome da pessoa.

   * @param cpf O CPF da pessoa.

   */

  public Pessoa(String nome, String cpf) {

    this.nome = nome;

    this.cpf = cpf;

  }

 

  /**

   * Constroi uma pessoa com nome dado e sem CPF.

   * @param nome O nome da pessoa.

   */

  public Pessoa(String nome) {

    this(nome, "");

  }

 

  /**

   * Recupera o nome da pessoa.

   * @return O nome da pessoa.

   */

  public String getNome() {

    return nome;

  }

 

  /**

   * Recupera o CPF da pessoa.

   * @return O CPF associado à pessoa.

   */

  public String getCPF() {

    return cpf;

  }

 

  /**

   * Ajusta o nome da pessoa.

   * @param nome O nome da pessoa.

   */

  public void setNome(String nome) {

    this.nome = nome;

  }

 

  /**

   * Ajusta o CPF da pessoa.

   * @param cpf O CPF associado à pessoa.

   */

  public void setCPF(String cpf) {

    this.cpf = cpf;

  }

 

  /**

   * Representa a pessoa como string

   */

  @Override

  public String toString() {

    return "Nome " + nome + ", cpf " + cpf;

  }

 

  /**

   * Testa a igualdade de um objeto com esta pessoa.

   * @param objeto O objeto a comparar com esta pessoa.

   * @return true se o objeto for igual a esta pessoa, false caso contrário.

   */

  @Override

  public boolean equals(Object objeto) {

    if(! (objeto instanceof Pessoa)) {

      return false;

    }

    Pessoa outra = (Pessoa)objeto;

    return getNome().equals(outra.getNome())

            && getCPF().equals(outra.getCPF());

  }

}

 

    Note que não foi preciso declarar explicitamente que a classe Pessoa deveria ser uma subclasse da classe Object

    Os métodos toString e equals da classe Pessoa estão sobrescrevendo os métodos toString e equals da classe Object. Veja o código a seguir:

Pessoa pessoaRaquel1 = new Pessoa("Raquel V. Lopes", "1234567-88");

System.out.println(pessoaRaquel1.toString());

Pessoa pessoaRaquel2 = new Pessoa("Raquel Lopes", "2345678-99");

System.out.println(pessoaRaquel1.equals(pessoaRaquel2));

    Os métodos toString e equals chamados são de que classe?

       São os métodos da classe Pessoa, que sobrescrevem os métodos toString e equals da classe Object. A anotação @Override indica esta “sobrescrita”. 

    Caso os métodos toString e equals não tivessem sido definidos em Pessoa, o que aconteceria quando eles fossem invocados para um objeto Pessoa?

       Os métodos da classe mãe seriam herdados

       Não teria como comparar duas pessoas, pois o equals da classe objeto só sabe comparar dois objetos genéricos e faz comparação de referências, não usa valores lógicos

       O toString imprimiria o endereço de memória do objeto pessoa (que é o que o toString de Object faz)

    @Override é uma anotação que serve para informar ao compilador que este método sobrescreve um método da classe base

       É sempre bom usar @Override quando queremos sobrescrever uma classe. O compilador nos ajuda a não cometer erros. Se um parâmetro for trocado, ou for erroneamente declarado como de outro tipo, por exemplo, não estaríamos sobrescrevendo o método da classe mãe, estaríamos apenas criando um novo método na classe filha

       Ao fazer override de método tenha certeza de que a assinatura do método está idêntica à assinatura do método na classe base; caso contrário você estará apenas adicionando um novo método a sua nova classe

       Herdamos de classes que têm comportamento bastante parecido com o comportamento da classe que estamos escrevendo, mas podemos modificar este comportamento quando necessário

i)    Fazendo override de métodos

ii)Acrescentando novo comportamento (novos métodos)

    Veja a seguir a implementação da classe Funcionário

package p2.exemplos;

 

/**

 * Representa um funcionário qualquer, que é uma pessoa que tem uma

 * matrícula, um salário e um tempo de serviço.

 *

 * @author Raquel Lopes

 *

 */

public class Funcionario extends Pessoa {

 

   private String matricula;

 

   private double salarioBase;

 

   private int tempoDeServico;

 

   /**

    * Cria um funcionário.

    *

    * @param nome

    *            O nome do funcionário.

    * @param cpf

    *            O CPF do funcionário.

    * @param matricula

    *            A matrícula do funcionário.

    * @param tempoDeServico

    *            O tempo de serviço (em meses) do funcionário.

    */

   public Funcionario(String nome, String cpf, String matricula,

         int tempoDeServico, double salarioBase) {

      super(nome, cpf);

      this.matricula = matricula;

      this.tempoDeServico = tempoDeServico;

      this.salarioBase = salarioBase;

   }

 

   /**

    * Este método computa o salário do funcionário.

    *

    * @return O salário do funcionário.

    */

   public double computaSalario() {

      return salarioBase;

   }

 

   /**

    * Recupera a matrícula do funcionário.

    *

    * @return A matrícula do funcionário.

    */

   public String getMatricula() {

      return matricula;

   }

 

   /**

    * Atribui uma nova matrícula ao funcionário.

    *

    * @param matricula

    *            O valor da nova matrícula.

    */

   public void setMatricula(String matricula) {

      this.matricula = matricula;

   }

 

   /**

    * Recupera o salário base do funcionário.

    *

    * @return O salário do funcionário.

    */

   public double getSalarioBase() {

      return salarioBase;

   }

 

   /**

    * Atribui um novo salário base ao funcionário.

    *

    * @param salario

    *            O novo salário do funcionário.

    */

   public void setSalarioBase(double salario) {

      salarioBase = salario;

   }

 

   /**

    * Recupera o tempo de serviço em meses do funcionário.

    *

    * @return O tempo de serviço do funcionário.

    */

   public int getTempoDeServico() {

      return tempoDeServico;

   }

 

   /**

    * Atribui um novo tempo de serviço ao funcionário que deve ser maior que

    * o tempo de serviço anterior.

    *

    * @param tempoDeServico

    *            Novo valor para tempo de serviço.

    */

   public void setTempoDeServico(int tempoDeServico) {

      if (tempoDeServico > this.tempoDeServico)

         this.tempoDeServico = tempoDeServico;

   }

 

   /**

    * Representa um funcionário como String.

    *

    * @return A string que representa um funcionário.

    */

   @Override

   public String toString() {

      return super.toString() + ", matricula " + getMatricula();

   }

 

   /**

    * Testa a igualdade de um objeto com este funcionário. Dois objetos da

    * classe Funcionario são iguais se eles são a mesma pessoa e têm a mesma

    * matrícula.

    *

    * @param objeto

    *            O objeto a comparar com este funcionario.

    * @return true se o objeto for igual a este funcionario, false caso

    *         contrário.

    */

   @Override

   public boolean equals(Object objeto) {

      if( objeto == null || objeto.getClass() != this.getClass() ) {

         return false;

      }

 

      Funcionario func = (Funcionario) objeto;

      Pessoa pessoa = (Pessoa) objeto;

     

      return super.equals(pessoa)

             && getMatricula().equals(func.getMatricula())

             && getSalarioBase() == func.getSalarioBase()

             && getTempoDeServico() == func.getTempoDeServico();

   }

}

    Note que aqui tivemos que dizer explicitamente que a classe Funcionario deriva de uma outra classe

       Isto vai acontecer sempre que desejarmos que a classe mãe seja diferente de Object

    O que está acontecendo?

       É como se houvesse agora uma Pessoa dentro de Funcionario e como se Funcionario implementasse todos os métodos de Pessoa, com os mesmos parâmetros definidos na classe Pessoa, delegando a responsabilidade ao método original do objeto Pessoa que está dentro de Funcionario (chamamos a isso de wrapper)

       Mas isso tudo é feito automaticamente, pelo compilador, com o simples uso de extends

    Para que o objeto interno (neste caso, Pessoa) funcione bem, o que é necessário?

       Em outras palavras: para que possamos usar um atributo que é um objeto, o que temos que fazer antes de usá-lo? Vimos isso quando conversamos sobre composição

    A palavra reservada super é usada para se referir à superclasse da qual esta classe herda

       Semelhante ao this, mas neste caso para se referir ao objeto da classe mãe

       No construtor, podemos usar a palavra super para chamar o construtor da superclasse

i)    Isto é necessário para garantir que o seu objeto da classe base será corretamente instanciado aqui

       Note que dentro dos métodos toString e equals (que sobrescrevem métodos da classe Pessoa) fizemos chamadas aos métodos toString e equals da classe Pessoa usando a palavra reservada super

i)    Se não usássemos a palavra reservada super então os métodos toString e equals da classe base sendo definida seriam chamados

    Todos os métodos e campos da classe Pessoa foram então “herdados” da classe base

       Mas não podemos mexer nos campos diretamente pois eles são privados (private)

       Temos que usar os métodos accessor e mutator (gets e sets) para acessar os atributos

       É possível trabalhar diretamente com os campos herdados?

i)    Sim, desde que eles tenham visibilidade protected ou public

ii)Veja o exemplo a seguir

   //Na classe Pessoa:

   ...

   protected String nome;

 

   protected String cpf;

 

 

   //Na classe Funcionario:

   ...

 

   /**

    * Representa um funcionário como String.

    *

    * @return A string que representa um funcionário.

    */

   @Override

   public String toString() {

      return "Nome " + nome +

             ", cpf " + cpf +

             ", matricula " + matricula;

   }

    É possível usar os métodos diretamente, desde que os atributos da classe mãe tenham visibilidade protected ou public

       Na classe Pessoa, os atributos nome e cpf teriam visibilidade protected

       Membros protected são acessíveis à classe e às subclasses

       Isso é bom?

i)    Causa um acoplamento enorme entre as classes, significando que modificações na classe base podem requerer com freqüência modificações na classe derivada

ii)          Você precisa de uma ótima justificativa para colocar os atributos como protected!

    Note que os métodos equals e toString foram “overrided” (sobrescritos) pela classe Funcionario

       Para fazer override a assinatura dos métodos da classe base e da classe derivada devem ser idênticas

       Note que sobrescrita (override) é diferente de sobrecarga (overload)

i)    Em overload temos em uma mesma classe métodos com o mesmo nome, que recebem e parâmetros diferentes (em número e/ou tipo). Vimos bastantes exemplos de sobrecarga de construtores de uma classe

ii)Em override temos método da classe derivada que se sobrepõe ao método da classe mãe. O método sobrescrito (da classe base) e o método que sobrescreve (da classe derivada) têm exatamente os mesmos parâmetros

iii)        Em overload temos métodos com mesmo nome na mesma classe, mas parâmetros de entrada diferentes

iv)        Em override temos métodos com mesmo nome em classes diferentes (base e derivada), e com os mesmos parâmetros de entrada

       Usar @Override é bom… Porque o compilador avisa quando estivermos enganados, fazendo overload, em vez de override

 

    Ainda temos um probleminha… O cliente falou que para cada tipo de funcionário diferente existe uma forma diferente de computar o salário do funcionário

       O que a função computaSalario de Funcionario faz?

       É um método dummie… Não corresponde a uma realidade de fato

       Uma possibilidade seria retirar este método. Mas gostaríamos que toda classe derivada de Funcionario tivesse obrigatoriamente um método que sabe como computar o salário. Como garantir a presença deste comportamento nas classes derivadas, sem precisar escrever algo dummie na classe base?

i)    A solução mais elegante é fazer a classe Funcionario ser uma classe abstrata

ii)Existe pelo menos um método que não tem implementação, tem apenas sua assinatura

iii)        Não se pode fazer new para criar um objeto de uma classe abstrata

iv)        Qualquer classe que deriva de uma classe abstrata fica obrigada a implementar os métodos não implementados ou a ser abstrata também

v)Veja abaixo como definir Funcionario como uma classe abstrata

package p2.exemplos;

 

/**

 * Representa um funcionário qualquer, que é uma pessoa que tem uma

 * matrícula, um salario e um tempo de serviço.

 *

 * @author Raquel Lopes

 *

 */

public abstract class Funcionario extends Pessoa {

 

   private String matricula;

   private double salarioBase;

   private int tempoDeServico;

 

   /**

    * Cria um funcionário.

    *

    * @param nome

    *            O nome do funcionário.

    * @param cpf

    *            O CPF do funcionário.

    * @param matricula

    *            A matrícula do funcionário.

    * @param tempoDeServico

    *            O tempo de serviço (em meses) do funcionário.

    */

   public Funcionario(String nome, String cpf, String matricula,

         int tempoDeServico, double salarioBase) {

      super(nome, cpf);

      this.matricula = matricula;

      this.tempoDeServico = tempoDeServico;

      this.salarioBase = salarioBase;

   }

 

   /**

    * Este método computa o salário do funcionário.

    *

    * @return O salário do funcionário.

    */

   public abstract double computaSalario();

 

   /**

    * Recupera a matrícula do funcionário.

    *

    * @return A matrícula do funcionário.

    */

   public String getMatricula() {

      return matricula;

   }

 

   /**

    * Atribui uma nova matrícula ao funcionário.

    *

    * @param matricula

    *            O valor da nova matrícula.

    */

   public void setMatricula(String matricula) {

      this.matricula = matricula;

   }

 

   /**

    * Recupera o salário base do funcionário.

    *

    * @return O salário do funcionário.

    */

   public double getSalarioBase() {

      return salarioBase;

   }

 

   /**

    * Atribui um novo salário base ao funcionário.

    *

    * @param salario

    *            O novo salário do funcionário.

    */

   public void setSalarioBase(double salario) {

      salarioBase = salario;

   }

 

   /**

    * Recupera o tempo de serviço em meses do funcionário.

    *

    * @return O tempo de serviço do funcionário.

    */

   public int getTempoDeServico() {

      return tempoDeServico;

   }

 

   /**

    * Atribui um novo tempo de serviço ao funcionário que deve ser maior

    * que o tempo de serviço anterior.

    *

    * @param tempoDeServico

    *            Novo valor para tempo de serviço.

    */

   public void setTempoDeServico(int tempoDeServico) {

      if (tempoDeServico > this.tempoDeServico)

         this.tempoDeServico = tempoDeServico;

   }

 

   /**

    * Representa um funcionário como String.

    *

    * @return A string que representa um funcionário.

    */

   @Override

   public String toString() {

      return super.toString() + ", matricula " + getMatricula();

   }

 

   /**

    * Testa a igualdade de um objeto com este funcionário. Dois objetos da

    * classe Funcionario são iguais se eles são a mesma pessoa e têm a mesma

    * matrícula.

    *

    * @param objeto

    *            O objeto a comparar com este funcionario.

    * @return true se o objeto for igual a este funcionario, false caso

    *         contrário.

    */

   @Override

   public boolean equals(Object objeto) {

      if (!(objeto instanceof Funcionario)) {

         return false;

 

      }

      Funcionario func = (Funcionario) objeto;

      Pessoa pessoa = (Pessoa) objeto;

      return super.equals(pessoa)

            && getMatricula().equals(func.getMatricula());

   }

}

    Note que o método computaSalario não tem corpo e é definido como abstract

    Vejamos agora a implementação da classe Programador

package p2.exemplos;

 

import java.util.ArrayList;

import java.util.List;

 

public class Programador extends Funcionario {

 

   private List<String> linguagensEmQuePrograma;

   private String linguagemDePreferencia;

   private Projeto projetoAtual;

 

   public Programador(String nome, String cpf, String matricula,

         int tempoDeServico, String preferencia, int salarioBase) {

      super(nome, cpf, matricula, tempoDeServico, salarioBase);

      linguagensEmQuePrograma = new ArrayList<String>();

      linguagemDePreferencia = preferencia;

   }

 

   /**

    * Adiciona uma nova linguagem de programação conhecida pelo programador.

    *

    * @param lp

    *            A nova linguagem de programação que o programador conhece.

    */

   public void adicionaLinguagemConhecida(String lp) {

      if (!linguagensEmQuePrograma.contains(lp))

         linguagensEmQuePrograma.add(lp);

   }

 

   /**

    * Indica a participação do programador em um projeto.

    *

    * @param projeto

    *            O projeto em que o programador está inserido.

    */

   public void atribuiProjeto(Projeto projeto) {

      projetoAtual = projeto;

   }

 

   /**

    * Este método computa o salário do programador.

    *

    * @return O salário do funcionário;

    */

   @Override

   public double computaSalario() {

      return getSalarioBase() * 1.5;

   }

 

   /**

    * @return the linguagemDePreferencia

    */

   public String getLinguagemDePreferencia() {

      return linguagemDePreferencia;

   }

 

   /**

    * @param linguagemDePreferencia

    *            the linguagemDePreferencia to set

    */

   public void setLinguagemDePreferencia(String linguagemDePreferencia) {

      this.linguagemDePreferencia = linguagemDePreferencia;

   }

 

   /**

    * Retorna a representação deste objeto em String.

    *

    * @return A string que representa este objeto.

    */

   @Override

   public String toString() {

      return "Nome " + getNome() + ", cpf " + getCPF() + ", matricula "

            + getMatricula() + ", projeto " + projetoAtual.getTitulo();

   }

}

    Note que cada vez que criamos uma classe derivada implementamos apenas o que é diferente e esquecemos o que é igual

       É preciso alguma experiência para extrair o que é de fato igual

       Programming by difference! Veja aqui!

    Seguindo o mesmo raciocínio podemos escrever a classe Coordenador e FuncionarioAdministrativo

       Fica como exercício para casa

    Note que adicionamos comportamento novo à classe filha: setLinguagemDePreferencia, getLinguagemDePreferencia, atribuiProjeto e , adicionaLinguagemConhecida.

    Note que mudamos alguns comportamentos da classe mãe que não nos convinha: toString e computaSalario

    Poderíamos também ter criado um novo método equals específico da nova classe derivada (override de equals)... Fica também como exercício pra casa

Como representar herança em UML?

    A relação de herança que existe entre classes pode ser representada como mostramos abaixo usando UML

    Observe que uma classe abstrata tem seu nome em itálico

    Fica óbvio nesta figura que criamos uma hierarquia de classes

Escolhendo entre herança e composição

MCj04280830000[1](volte a ler esta seção inteira depois que você entender interfaces e polimorfismo!)

 

 

    Composição e herança são dois mecanismos para reusar implementação

    Alguns anos atrás (e na cabeça de alguns programadores ainda!), a herança era considerada a ferramenta básica de extensão e reutilização de funcionalidade

    Hoje, considera-se que a composição é muito superior à herança na maioria dos casos

       A herança deve, claro, ser utilizada em alguns contextos

    Quando for usar herança, tenha certeza que existe uma relação “é um

       Essa relação, na verdade indica que a subclasse “é um tipo especial” da superclasse

       É comum confundir a relação “é um” com a relação “exerce papel de”

       A relação “é um” permanece constante durante toda a execução da aplicação e de preferência durante toda a vida do software

       Quando o efeito que você pretende é criar uma classe que expõe a mesma interface (o mesmo contrato) que a classe base, porém é uma especialização (um caso especial, com algumas diferenças) da classe base

i)    Herança não faz sentido quando essa relação não for verdadeira!

       O que você acha do exemplo visto em sala? Funcionario é um tipo especial de pessoa? Programador é um tipo especial de Funcionario?

i)    BicicletaGeneria e CaloiCeci, BMX, MountainBike

ii)Cachorro e Boxer, Labrador e PastorAlemao

iii)        Transacao, Reserva e Compra

    Nunca use herança apenas para ter reuso de código

    Nunca use herança apenas para ter polimorfismo, isso você obtém usando interfaces!

    Quando usar composição?

       Sempre que você for reusar e não for cabível usar herança (isso vai ser a maior parte dos casos)

       Dentro de sua classe existe funcionalidade de outra classe, mas a interface que você expõe é a interface própria que você criou para a sua nova classe

       Tipicamente, não existe a relação “é um” mencionada acima

i)    Por exemplo, Conta não é uma Pessoa, mas uma Pessoa é um titular de uma conta, então a Conta embute comportamento de Pessoa dentro dela no que diz respeito ao titular

ii)Funcionario é um papel exercido por uma pessoa, assim como Programador é um papel exercido por um funcionario

    As grandes vantagens da composição são:

       A herança gera um acoplamento muito forte, especialmente se você expõe os atributos para as subclasses (usando protected, por exemplo)

       A composição pode reduzir acoplamento entre as classes, principalmente se interfaces forem usadas (lembram que definimos List<E> lista = new ArrayList<E>() em vez de  ArrayList<E> lista = new ArrayList<E>()??)

i)    Voltaremos a falar disso depois! (Cobrem!!!)

       Mesmo que a relação “é um” exista, podemos tomar por questões de desacoplamento de código a decisão de usar composição

ii)Exemplo da Fruta, Laranja e Descasca!

       A composição pode ser mudada em tempo de execução

    Vamos falar um pouco mais sobre o acoplamento entre classe e subclasse

       O que ocorre quando uma representação interna de um campo muda na classe mãe?

i)    Se este campo for visível às subclasses, pode quebrar as subclasses

       O que acontece se ocorrer uma mudança na forma como um método da classe mãe é implementado?

i)    As subclasses que esperavam outro comportamento podem quebrar

(1)                 Isso também poderia ocorrer com composição, se você não estiver usando interfaces

       O que acontece se a interface da classe mãe muda?

i)    Se métodos forem removidos, então as classes filhas podem quebrar

ii)Se a classe mãe for abstrata, novos métodos abstratos obrigarão escrita de novos métodos na classe filha

iii)        Se métodos forem adicionados é preciso re-analisar a herança e decidir se deverá ou não haver sobrescrita de alguns

(1)                 Um novo método na classe mãe pode causar problemas na ordem da lógica do programa

(2)                 Exemplo: você está escrevendo uma classe que representa um zoológico cujo dono é contra manter animais selvagens em cativeiro e por isso obriga-se a ter em seu zoológico uma espécie de cada animal selvagem

(a)     Você herda da classe Zoologico previamente escrita e testada por alguém

(b)    Você sobrescreve (faz override) todos métodos que adicionam animais ao zoológico para garantir que não há duplicatas de animais selvagens no seu zoológico contra cativeiro

(c)      Depois você usa a sua classe ZoologicoAmigo (que extends Zoologico)

(d)    Um belo dia, o cara que escreveu a classe Zoologico escreve mais um método, que adiciona um animal ao zoologico com base em parâmetros de entrada diferente

(e)     E aí? O que acontece com sua classe ZoologicoAmigo?

(f)        Os programas cliente da classe ZoologicoAmigo têm a chance de adicionar um animal usando o novo método que você não sobrescreveu (ninguém te avisou que a classe Zoologico mudou, chato não é?)

(g)     Se houvesse um zoologico dentro de zoologicoAmigo, este problema não existiria ;)

(h)    Você só teria que garantir que na classe ZoologicoAmigo nenhum animal duplicado é adicionado ao Zoologico

(i)         Você exporia apenas os métodos que desejasse

(j)         Caso um novo adicionarAnimal surgisse em Zoologico nenhum usuário de sua classe ZoologicoAmigo seria capaz de usá-lo

(k)     É como se houvesse uma barreira entre Zoologico e programas clientes de ZoologicoAmigo, de forma que mudanças em Zoologico podem afetar apenas ZoologicoAmigo, mas não seus clientes

(l)         Um ZoologicoAmigo é um Zoologico… Mas nesse caso, usar herança não é a melhor opção!

    Se a interface da classe sendo reusada por composição mudar, isto também poderá quebrar o código cliente (se houver remoção de métodos)

       Métodos marcados como deprecated

i)    Pra dar tempo para a mudança

    Uma grande vantagem da herança é que ela muitas vezes gera código mais simples de ler e entender

       A composição usa muitos objetos pequenos e só sabemos quem vai ser composto com quem em tempo de execução

i)    Isso pode ser mais difícil de entender

Comparando composição e herança

    É mais fácil mudar a interface de classes back-end (composição) do que a interface de superclasses (herança)

       Mudança na interface da classe de back-end não implica necessariamente em mudança na interface da classe front-end

       Código que depende apenas da interface da classe front-end não precisa ser mudado

       Código que depende apenas da interface da subclasse pode precisar ser mudado

    É mais fácil mudar a interface de classes front-end (composição) do que a interface de subclasses (herança)

       Ao mudar a interface da subclasse tem que certificar-se de que ela continua compatível com a interface de suas superclasses

       É proibido adicionar em uma subclasse métodos com mesmo nome e parâmetros de entrada da superclasse, porém com tipo de retorno diferentes

    Ao usar composição você pode realizar lazy instantiation dos objetos bac-kend, pode mudá-los em tempo de execução

       Com herança o objeto da subclasse que fica ali, automaticamente embutido, é criado assim que o objeto da superclasse é criado e permanece o mesmo ao longo de toda a vida do objeto

i)    Veremos exemplos disso no futuro!

    A delegação explicita que ocorre ao fazer uso da composição pode ter algum impacto no desempenho do objeto de front-end

       Ele poderia ser mais rápido se fosse uma subclasse e não tivesse que fazer delegação

    Quando se muda a implementação da classe mãe ou da classe front-end, sem mudar a interface, composição e herança não diferem

Qual é o aspecto mais importante da herança?

    Não é o simples reuso! Isso é bom… Mas existem conseqüências de se usar herança

    O aspecto mais importante é o relacionamento que há entre a nova classe e a classe base

       A nova classe é um tipo da classe existente

       Veremos mais adiante como isto é revolucionário!!

    Criamos 5 classes formando uma hierarquia

       Examinando uma hierarquia, podemos dizer que uma subclasse é um tipo da classe mãe

       Galinha "é um tipo de" Ave

       Laranja "é um tipo de" Fruta

       CaloiCeci “é um tipo de” Bicicleta

    Isto nos dá o poder de substituição de um objeto por outro

    Liskov Substitution Principle (LSP)

       Se S é um subtipo de T, então objetos do tipo T em um programa podem ser substituídos por objetos do tipo S sem alterar as propriedades e corretude do programa

    A relação "É um tipo de" implica que tudo que um objeto da classe mãe faz pode ser feito por um objeto da subclasse

       Portanto, onde um objeto da classe é esperado, podemos colocar um objeto de uma subclasse

       Significa "habilidade de permitir a substituição"

    Exemplo: nas linhas seguintes ...

(1)                 Ave ave = new Ave(…);

(2)                 ave.alimenta (racao);

(3)                 ave.getPeso();

    ... ave é um Ave

    Mas tudo funcionaria perfeitamente se fizéssemos ave = new Galinha(...)

    Em outro exemplo, suponha que um método espera uma Ave. Este método pode receber uma galinha, sem problemas:

       public int verificaNumeroDeFilhos (Ave ave)

       Galinha galinha = new Galinha(…);

       donoDoZoo.verificaNumeroDeFilhos(galinha);

Upcasting e downcasting

    Olhando a hierarquia de classes

       Receber uma classe mais baixa na hierarquia (derivada) e usar como se fosse uma de mais alto nível na hierarquia

i)    Upcasting

ii)Sempre dá certo, pois a classe de baixo é um tipo da classe de cima

       Receber uma classe de mais alto nível (superclasse) e fazer um casting para uma classe de mais baixo nível

i)    Downcasting

ii)Nem sempre dá certo, só se o objeto recebido for realmente da classe para a qual fizemos o casting

iii)        Tipicamente fazemos downcasting no método equals, depois que temos certeza que temos um objeto de determinada classe na mão

   public boolean equals(Object objeto) {

      if (!(objeto instanceof Funcionario)) {

         return false;

 

      }

      Funcionario func = (Funcionario) objeto;

      return super.equals(func)

            && getMatricula().equals(func.getMatricula());

   }

    Neste equals em especial, temos também um upcasting. Onde?

    A controvérsia do equals

       Se usamos getClass podemos quebrar o principio de Liskov

       O fato é que é impossível criar subclasses que adicionam valor à classe, manter o contrato do equals e obedecer Liskov!

       Alguns programadores preferem infringir Liskov, outros preferem infringir o contrato do equals – cada caso é um caso!

O uso da palavra reservada final

    Até agora, como usamos esta palavra?

    Se vier em um método

       Indica que ele não pode ser sobrescrito

       Tipicamente por questão de projeto (design)

       Você quer garantir que este método sempre terá a implementação original

       Será possível imaginar todas as formas como esta classe poderá ser reusada no futuro?

       Já foi usado no passado por questões de desempenho

i)    Voltaremos a esta questão mais adiante, quando estivermos falando de polimorfismo

    Se vier em um argumento de um método

       Indica que este argumento não pode mudar dentro do método

    Se você tiver uma constante que não é um tipo primitivo, é uma referência a um objeto

       A referência fica fixa e deve apontar para o mesmo objeto

       O objeto em si pode mudar

i)    Por exemplo, podemos fazer um set em um de seus atributos…

ii)Mas a constante sempre apontará para este objeto

iii)        Não pode de repente passar a referenciar um outro objeto

    Se tivermos uma classe final?

       Ela não pode servir de classe mãe para ninguém! É garantidamente uma classe folha na hierarquia

 

Voltar