Boas práticas com Orientação a Objetos Paulo Silveira paulo.silveira@caelum.com.br
Rapidissímo overview IFs e switches X herança Vantagens e desvantagens do uso de Herança Casos errados do uso de herança Herança versus Composição Herança versus Interfaces Programando pensando em Interfaces
Antes de qualquer coisa: Não estou falando mal do seu código!
Switchs e IFs Usando polimorfismo
Caso trivial class Funcionario { private int codigo; public static final int DIRETOR = 1; public static final int PRESIDENTE = 2; //... public int getcodigo() { return this.codigo;
Em algum outro lugar.. class Relatorio { private double dinheirogasto; private Empresa empresa; //... public void adiciona(funcionario f) { switch (f.getcodigo()) { case Funcionario.DIRETOR : this.gastos += 1.1 * f.getsalario(); break; case Funcionario.PRESIDENTE : this.gastos += f.getsalario() + empresa.getpartedoslucros(); break; Esse tipo de código vai se espalhar por toda aplicação! Ao criar uma nova filha de Funcionario, precisamos mudar esse(s) código(s)!
Remodelando Funcionario class Funcionario { public double getgastos() { return this.salario; class Diretor extends Funcionario { public double getgastos() { return this.salario * 1.1; Diretor Presidente Cálculo dos gastos concentrado dentro da própria classe
E o relatório... class Relatorio { private double dinheirogasto; private Empresa empresa; //... public void adiciona(funcionario f) { this.gastos += f.getgastos(); Para adicionar uma nova classe que extende Funcionario, não teremos mais de mexer na classe Relatorio!
Controle de Permissões class Usuario { private int id; private String name; //... O uso de herança nesse caso é altamente dicutível! Moderador Usuario Administrador Estamos usando apenas como exemplo.
Primeira tentativa Ifs encadeados num estilo switch(int) public void apagaregistro() { if(this.user instanceof Usuario) { return false; if(this.user instanceof Moderador) { return false; if(this.user instanceof Administrador) { return true; Caso uma classe Colaborador fosse adicionada, precisaríamos mexer aqui também!
Usando polimorfismo Usuario Moderador Administrador class Usuario { public boolean hasright(int code) {
Apagando com a nova forma public void apagaregistro() { if(this.user instanceof Usuario) { return false; if(this.user instanceof Moderador) { return false; if(this.user instanceof Administrador) { return true; Passa para: public void apagaregistro() { if(this.user.hasright(permissions.delete){ return false;
E ao criarmos a classe Colaborador... class Colaborador extends Usuario { public boolean hasright(int code) { // monte de ifs e switchs Código de permissões não esta mais concetrado em um único lugar, cada classe é responsável pelo seu.
Pequena conclusão Switch é uma anomalia Polimorfismo é muito mais elegante Você muda apenas na própria classe, não precisa concentrar lógica de negócio. Switch é feio! Isto não é uma regra rígida!
Vantagens da Herança
Herança, quais são as vantagens? Reaproveitamento de código Possibilita polimorfismo Vamos mostrar que podemos ter esses benefícios de outras maneiras mais leves.
Quando não usar herança Apredendo com os erros da Sun
O que vocês acham dessas classes? java.util.properties java.util.stack
Properties public class Properties extends Hashtable Properties é uma Hashtable? Hashtable: associa Object Object Properties: associa String - String Properties não é uma Hashtable!
Properties Object Dictionary Hashtable Properties
O que acontece com esse código? Object object = new Object(); Hashtable table = new Properties(); table.put( x, object); table.get( x ); Funciona ok!
Um pouco pior... Object object = new Object(); Properties table = new Properties(); table.put( x, object); table.getproperty( x ); table.get( x ); Compila perfeitamente, mas só retorna o object quando chamado pelo.get, e pelo getproperty volta Null. Como adivinhariamos isso? Parece então que são duas tabelas separadas. Uma para Object- Object, e outra para String String
Último round Properties table = new Properties(); table.put( x, y ); table.getproperty( x ); Retorna null também? Não, dessa vez acontece o esperado.
java.util.stack Caso parecido Se é uma pilha, porque posso remove(int)? Porque posso add(int, Object)? Herdou apenas para não ter de escrever um vetor! PREGUIÇA!
Pequena conclusão Nunca usar herança quando a relação não é claramente é um. Contratos acabam sendo quebrados Difícil de mudar depois Pergunte a si mesmo se você não está fazendo isso por pura preguiça!
Lembram-se desse caso? Moderador é um Usuario? Usuario Usuario as vezes esta no papel de moderador ou administrador, mas a relação não é exatamente é um. Moderador Administrador
Classe Entity class Entity { private int id; private String name; private String description; // gets e sets class Usuario extends Entity { é um faz sentido? É um estado de Usuário? É um papel de Usuário? Veremos como resolver isso com composição!
Problemas trazidos pela herança
Considere Funcionario Diretor Presidente
Uso do protected class Funcionario { protected double salario; protected int idade; class Diretor extends Funcionario { public double getgastos() { if(this.idade < 35) { return salario; else { return salario * 1.1;
Uso do protected class Funcionario { protected double salario; protected int idade; O que acontece com a classe Diretor? Não compila? Isto não é o mais grave, ele poderia estar em outro jar, e ele vai começar a lançar Errors class Funcionario { protected double salario; protected Calendar nascimento;
Uso do protected Classe filha precisa conhecer bem a mãe Classe mãe não pode mudar sua representação interna com facilidade! Quebra de encapsulamento. Protected define um contrato forte para a mãe que não tem muito utilidade para quem esta de fora! Métodos protected não são tão malvados assim
Considere Pessoa Pessoa Física Pessoa Jurídica
Método na mãe muda Quando um método da mãe muda pode quebrar o comportamento da classe filha
Conhecemos a interface publica da classe class PessoaJuridica { public void adicionanota(nota nota) { public void adicionavariasnotas(list<nota> notas) { Não conhecemos bem essa classe!
Fornecedor quer saber quantas transcoes! class FornecedorJuridica extends PessoalJuridica { private int transacoes; public void adicionanota(nota nota) { this.transacoes++; Funciona? super.adicionanota(nota); public void adicionavariasnotas(list<nota> notas) { this.transacoes += notas.size(); super.adicionavariasnotas(notas); public int gettransacoes() { return this.transacoes; List notas =... // lista com 3 notas f.adicionavariasnotas(notas); f.gettotaltransacoes();
Depende! List notas =... // lista com 3 notas f.adicionavariasnotas(notas); f.gettotaltransacoes(); class PessoaJuridica { Resultado? public void adicionanota(nota nota) { this.vetorzinho[posicao++] = nota; public void adicionavariasnotas(list<nota> notas) { for(nota n : notas) { this.vetorzinho[posicao++] = n;
Mudança na classe mãe! List notas =... // lista com 3 notas f.adicionavariasnotas(notas); f.gettotaltransacoes(); E agora que mamãe mudou? class PessoaJuridica { public void adicionanota(nota nota) { this.vetorzinho[posicao++] = nota; public void adicionavariasnotas(list<nota> notas) { for(nota n : notas) { //... this.adicionanota(n); A resposta é 6!
Grande árvore de herança, mais problemas Pessoa Pessoa Física Pessoa Jurídica Fornecedor Físico Fornecedor Jurídico
Herança, lados negativos Você ganha mais coisas do que gostaria Sua relação com sua mãe é conturbada Quebra de encapsulamento Não use herança apenas por reaproveitamento de código da classe mãe! Não use herança apenas pelo polimorfismo!
Herança versus Composição Usando polimorfismo
Problemas! Além dos já apresentados: Pessoa Fornecedor é Pessoa? Pessoa Física Pessoa Jurídica Aqui tem código repetido! Fornecedor Físico Fornecedor Jurídico
FornecedorJuridico é Pessoa? É sim, mas estamos utilizando algo como Pessoa p = new Fornecedor()? Ou recebendo Fornecedor como Pessoa? Aumentado a hierarquia, como representaríamos alguem como Fornecedor e Consumidor?
Diminuindo acomplamento Pessoa Fornecedor Físico Pessoa Física class FornecedorFisico extends PessoaFisica { passa para: class FornecedorFisico { private PessoaFisica pessoa; // metodos para delegar
Acabando com código duplicado Pessoa Fornecedor Físico Pessoa Física Pessoa Jurídica Fornecedor Jurídico public String peganome() { public void envianotificacaosobrecredito() {
Acabando com código duplicado Pessoa Fornecedor Físico Pessoa Física Pessoa Jurídica Fornecedor Jurídico Fornecedor Helper
FornecedorHelper Faz o trabalho comum aos Fornecedores Pode ser uma classe package-friendly class FornecedorHelper { private String nome; private String email; public String peganome() { public void envianotificacaosobrecredito() { class FornecedorJuridico { private FornecedorHelper helper; public void envianotificacaosobrecredito() { this.helper.envianotificacaosobrecredito();
Vantagens do uso da composição Menor acoplamento, difícil de quebrar Quem faz e como faz esta escondido Implementação pode ser trocada em runtime
Antes de usar herança Estou fazendo por preguiça? É uma relação de é um? Não é um estado em que a classe pode ficar? Não é um papel que a classe pode tomar?
Será que isso pode causar transtornos? Object Component Container JComponent JPanel JSpinner.DefaultEditor JSpinner.DateEditor
Herança versus Interfaces Programando voltado para interfaces
E se eu precisasse de polimorfismo? Utilização de Interfaces Reaproveitamento por composição Classes abstratas na hora da preguiça! Talvez package-friendly
Entity como interface interface Entity { Entity int getid(); Usuario class Usuario implements Entity { //... public int getid() { return this.id;
Entity melhorada Entity Usuario EntityInfo class Usuario implements Entity { private EntityInfo info; public int getid() { return this.info.getid();
Temos de escrever o esqueleto sempre? Entity Abstract Entity EntityInfo Usuario
Regras do Gang of Four 1. Prefira composição em vez de herança 2. Programe pensando nas interfaces e não na implementação
Leitura recomendada Design Patterns, Gang of Four Effective Java, Joshua Bloch Refactoring, Martin Fowler
Obrigado! Perguntas e Respostas Você pode ver esta palestra em: http://www.paulo.com.br/ Agradecimentos: Nos vemos no BrasilOne! http://www.brasilone.com.br/