Composição, Herança, etc.
• 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
• 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
• 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???
• 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.
• 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…
• 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
• 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 |
• 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…
• 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
• 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
• 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
(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
• 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);
• 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!
• 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