Ricardo Massa F. Lima [email protected]
Sérgio C. B. Soares [email protected]
Classes Abstratas e Interfaces AULA 12
Introdução a Programação – IF669 http://www.cin.ufpe.br/~if669
Até aqui
n
Quando usar herança?
n
Ao redefinir um método manter o
Adivinhem...
n
Surge um novo requisito na aplicação
bancária
n
Temos de cobrar um imposto em
certos tipos de contas
numero saldo
21.342-7
875,32
Objeto Conta Imposto
creditar
Número getSaldo 21.342-7 875,32 Crédito Débito
Estados do Objeto Conta
Imposto
numero saldo 21.342-7 875,00 debitar(20) creditar debitar Número getSaldo 21.342-7 875,32 Crédito Débito numero saldo 21.342-7 854,98 creditar debitarConta Imposto: Assinatura
Sem herança
public class ContaImposto {
public ContaImposto (String numero) {} public void creditar(double valor) {} public void debitar(double valor) {} public String getNumero() {}
public double getSaldo() {} }
Conta Imposto: Assinatura
Com herança
public class ContaImpostoM extends Conta { public ContaImpostoM(String numero) {} public void debitar(double valor) {} }
Conta Imposto: Descrição
Com herança
public class ContaImpostoM extends Conta { private static final double CPMF = 0.001; public ContaImpostoM (String numero) {
super (numero); }
public void debitar(double valor) { double imposto = (valor * CPMF);
super.debitar(valor + imposto); }
Subtipos e Subclasses
ContaImposto
Subclasses e
Comportamento (1)
n
Objetos da subclasse devem se comportar
como os objetos da superclasse
• Afinal de contas queremos usar objetos da
subclasse onde os objetos da superclasse são utilizados
public class Banco {
private Conta[] contas; private int indice; // ...
n
Redefinições de métodos devem
preservar o comportamento
(semântica) do método original
• No que diz respeito ao comportamento (e
atributos) herdado
n
Grande impacto sobre manutenção/
evolução de software...
Subclasses e
Revisão/Otimização de
Código
... double m(Conta c) { c.creditar(10); c.debitar(10); return c.getSaldo(); } ...Considerando apenas o retorno do método m, as duas opcões são sempre equivalentes?
Em que contextos? ... double m(Conta c) { return c.getSaldo(); } ...
Subclasses e Evolução de
Software
n
Deveria ser possível raciocinar sobre o
código usando-se apenas a definição dos
tipos das variáveis envolvidas (Conta)
n
O comportamento do código deveria ser
independente do tipo do objeto (Conta,
ContaEspecial, ContaImposto)
associado a uma dada variável em tempo
de execução
Reuso sem Subtipos
Conta
Poupanca ContaImpostoM
ContaEspecial
ContaImpostoM muda a semântica do método debitar e, se herdar de Conta, quebra a noção de subtipos!!!
Qual a alternativa então?
n O que existe de comum entre Conta e
ContaImposto?
• Vamos criar uma nova classe que
contenha essa parte em comum
• Conta e ContaImposto devem herdar
dessa nova classe
n Atenção: debitar é diferente nas duas
classes
• Mas ambas as contas devem permitir
Reuso preservando
Subtipos
ContaAbstrata ContaImposto Conta Poupanca ContaEspecialDefinindo Classes Abstratas
public abstract class ContaAbstrata { private String numero;
private double saldo;
public ContaAbstrata (String numero) { this.numero = numero;
this.saldo = 0.0; }
public void creditar(double valor) { this.saldo = this.saldo + valor; }
public double getSaldo() { return this.saldo;
}
public String getNumero() { return this.numero;
}
protected void setSaldo(double saldo) {
this.saldo = saldo; }
public abstract void debitar(double valor);
}
O método abstrato não tem implementação, mas
1. Permite programar (não executar), chamando o método da classe abstrata (na classe Banco, por exemplo)
2. Obriga que as subclasses concretas implementem o método
Revisão/Otimização de
Código
E agora, a modificação é correta? Em que contextos? ... double m(ContaA c) { c.creditar(10); c.debitar(10); return c.getSaldo(); } ... ... double m(ContaA c) { return c.getSaldo(); } ...
Classes Abstratas
n Possibilita herança de código preservando
comportamento (semântica)
• Não do método debitar, que ainda não possui
comportamento
n Métodos abstratos:
• geralmente, existe pelo menos um
• são implementados nas subclasses
n Não se cria objetos:
• mas devem ter construtores para reuso
• se necessário, métodos protected para serem
Contas: Descrição
Modificada
public class Conta extends ContaAbstrata { public Conta(String numero) {
super (numero); }
public void debitar(double valor) {
this.setSaldo(this.getSaldo() - valor); }
}
Implementação do método abstrato observe o uso do método protected
Poupanças: Descrição
Original
public class Poupanca extends Conta { public Poupanca(String numero) { super (numero);
}
public void renderJuros(double taxa) { this.creditar(this.getSaldo() * taxa); }
}
Conta Especial: Descrição
Original
public class ContaEspecial extends Conta { private static final double TAXA = 0.01; private double bonus;
public ContaEspecial (String numero) { super(numero);
this.bonus = 0.0; }
public void creditar(double valor) {
this.bonus = this.bonus + (valor * TAXA); super.creditar(valor);
}
Conta Imposto: Descrição
public class ContaImposto extends ContaAbstrata { private static final double CPMF = 0.001;
public ContaImposto (String numero) { super(numero);
}
public void debitar(double valor) { double imposto = valor * CPMF;
double total = valor + imposto;
super.setSaldo(this.getSaldo() – total);
} }
Implementação do método abstrato observe o uso do método protected
Substituição e Ligações
Dinâmicas
ContaAbstrata ca1, ca2;
ca1 = new ContaEspecial(“21.342-7”); ca2 = new ContaImposto(“21.987-8”); ca1.debitar(500);
ca2.debitar(500);
System.out.println(ca1.getSaldo()); System.out.println(ca2.getSaldo());
Classes Abstratas:
Utilização
n
Herdar código sem quebrar noção de
subtipos, preservando o comportamento
do supertipo
n
Generalizar código, através da abstração
de detalhes não relevantes
n
Projetar sistemas, definindo as suas
arquiteturas e servindo de base para a
implementação progressiva dos mesmos
Contas: Projeto OO
public abstract class ContaProjeto { private String numero;
private double saldo; //...
public abstract void creditar(double valor); public abstract void debitar(double valor);
public String getNumero() { return numero;
protected setSaldo(double saldo) { this.saldo = saldo;
}
//... }
Outro exemplo
Pessoa: Reuso e Subtipos
Pessoa
PessoaFisica PessoaJuridica
Pessoa: Projeto OO
public abstract class Pessoa { private String nome;
...
public abstract String getCodigo(); }
public class PessoaFisica
extends Pessoa { private String cpf;
...
public String getCodigo() { return cpf;
} }
public class PessoaJuridica
extends Pessoa { private String cnpj;
...
public String getCodigo() { return cnpj;
} }
public class RepositorioPessoasArray { private Pessoa[] pessoas;
...
public Pessoa procurar(String codigo) { Pessoa p = null;
boolean achou = false;
for (int i=0; i<indice && !achou; i++) { p = pessoas[i]; if (p.getCodigo().equals(codigo)) achou = true; else p = null; } return p; } }
Exercício
n Utilize a solução do último exercício
http://www.cin.ufpe.br/~if669/material/solucoes/aula12.zip
n Defina no pacote aula13.br.ufpe.cin.banco, a classe abstrata
ContaAbstrata que tem os mesmos atributos e métodos de
Conta, só que o método debitar é abstrato, como visto em sala
n Altere a classe Conta para herdar da classe ContaAbstrata e
implementar o método debitar
n Modifique a classe Banco para que seja possível trabalhar com
todos os tipos de conta da aplicação bancária. Execute a classe Programa e observe que o teste funciona como antes
n Defina a classe aula13.br.ufpe.cin.banco.ContaImposto que
herda de ContaAbstrata e tem uma constante CPMF que armazena o imposto a ser cobrado quando um valor for debitado na
ContaImposto
n É necessário alterar as classes Poupanca e ContaEspecial?
Encapsulamento e
Information Hiding
n
A habilidade em “esconder” de forma
segura dados e métodos de uma classe
dentro da “cápsula”da classe,
impedindo acesso de usuários não
confiáveis é conhecida como
information hiding
mas...por que estamos
Por que ecapsulamento com
information hiding
é útil?
n Esconder detalhes de implementação
• evita que outros programadores façam
uso dessas informações com algum propósito
• torna possível modificar a
implementação com a segurança de que não afetará o código que utiliza a classe
n Protege a classe de interferências externas
indesejáveis (sejam elas acidentais ou propositais)
• A classe contém conjunto de campos
interdependentes, que devem ser mantidos em um estado consistente
• Se for permitido a algum usuário externo (ou você
mesmo) modificar um campo sem modificar campos relacionados a ele, a classe ficará em um estado inconsistente
• Se, por outro lado, o acesso é feito via um método,
há mais chances de que o estado será mudado de maneira consistente
Por que ecapsulamento com
n Se todos os dados da classe estão
escondidos e podem ser acessados apenas via métodos da classe e esses métodos
foram bem testados, haverá maior garantia de que os dados serão modificados
consistentemente
n Se, por outro lado, for permitido acesso
externo, o número de possibilidades a
serem testadas torna-se não gerenciável.
Por que ecapsulamento com
n Se atributos públicos que são acessados
diretamente por outras classes forem modificados, as classes que os acessam diretamente serão afetadas
• Acessando os mesmos com métodos pode evitar
esse impacto
n Se um determinado método for definido
apenas para uso interno da classe,
esconder esse método evita que usuários da classe tentem usá-lo
Por que ecapsulamento com
Menos nobre, mas... se um campo ou método de uma classe for visível, você terá que
documentá-lo
economize seu tempo e esforço!
esconda os campos e métodos ao máximo!
Por que ecapsulamento com
Interfaces
n Através do encapsulamento, os atributos e a
implementação dos métodos de uma certa classe não são visíveis ao usuário da classe
n Conhecendo-se apenas a interface de uma classe, podemos
utilizar seus objetos sem conhecer detalhes de implementação
n Uma interface inclui os métodos disponíveis e suas
respectivas assinaturas
n Além disto, existem casos onde existe a necessidade de se
ter uma classe mas não queremos implementá-la
• pode-se terceirizar a implementação, fornecendo como
Interfaces - Exemplo
n Implementar um zoológico virtual com vários tipos de animais
n Você gostaria de enviar as seguintes mensagens a cada animal:
• nasca()
• passeie()
• durma()
• peso()
n Vamos pedir ajuda a programadores especialistas em cada
Interfaces - Exemplo
n O programador que for implementar o morcego terá que dizer
explicitamente que vai usar a interface Animal • palavra chave implements
A palavra chave implements
obriga o programador a escrever o código de todos os métodos na assinatura
Todos os métodos da interface devem ser públicos
Interfaces - Observação
Em cada arquivo deve existir no máximo uma classe pública! Logo, as classes Ornitorrinco, Morcego e Zebra devem estar em arquivos separados, com os respectivos nomes
Interfaces
Cada um dos animais, além de ser um objeto da própria classe, também é um objeto do tipo Animal
z2.contaListras() - Inválido z1.contaListras() -
Implementando mais de uma
interface por vez
n Considere as duas interface:
Implementando mais de uma
interface por vez
Implementando mais de uma
interface por vez
Finalmente, podemos ver o Aviao que
Mas e na aplicação bancária?
Onde usar interfaces?
n Hoje a classe Banco tem um array de Conta
n E se amanhã quisermos utilizar outra
estrutura de dados?
n E se quisermos depois de amanhã utilizar
um banco de dados?
n Vamos desacoplar as regras de negócio do
Criar uma interface de
armazenamento de dados
public interface RepositorioContas { void inserir(ContaAbstrata conta);
ContaAbstrata procurar(String numero); void remover(String numero);
void atualizar(ContaAbstrata conta); boolean existe(String numero);
}
Todos os métodos são public e abstract por
Repositório: Implementações
public class RepositorioContasArray
implements RepositorioContas {...} public class RepositorioContasLista
implements RepositorioContas {...} public class RepositorioContasVector
implements RepositorioContas {...} public class RepositorioContasBDR
Banco
: Parametrização
public class Banco {private RepositorioContas contas;
public Banco(RepositorioContas rep){ this.contas = rep;
}
public void cadastrar(ContaAbstrata conta){ String numero = conta.getNumero();
if (!contas.existe(numero)) { contas.inserir(conta);
} else {
throw new RuntimeException(“Já cad...”);
}
// ... }
A estrutura para armazenamento das contas é
O que usar? Quando?
Classes (abstratas)
n Agrupa objetos com
implementações compartilhadas
n Define novas classes
através de herança (simples) de código
n Uma classe pode ter
apenas uma como superclasse
Interfaces
n Agrupa objetos com
implementações diferentes
n Define novas interfaces
através de herança
(múltipla) de assinaturas
n Uma classe pode ter
n Utilize a solução dos exercícios da última aula
http://www.cin.ufpe.br/~if669/material/solucoes/aula13.zip
n Defina no pacote aula14.br.ufpe.cin.dados a interface
RepositorioContas com os métodos
• inserir – recebe uma ContaAbstrata e insere no repositório
• procurar – recebe um número e retorna a conta se estiver no repositório
• remover – recebe um número para remover a conta do repositório
• atualizar - recebe uma ContaAbstrata para atualizar no repositório
• existe – recebe um número e informa se existe uma conta com este
número no repositório
n Modifique a classe Banco para utilizar a interface definida
(receba a implementação da interface no construtor)
• Perceba que a classe Banco compila apenas com a interface. Já a
classe Programa precisa de uma implementação para executar.
n Defina no pacote aula14.br.ufpe.cin.dados classe
RepositorioContasArray que implementa a interface RepositorioContas
n Altere a classe Programa para fazer os testes