Classes Abstratas
e
Interfaces
Paulo Borba
Centro de Informática
Universidade Federal de Pernambuco
Objeto Conta Imposto
creditar
saldo
numero
875,32 21.342-7
debitar
Estados do Objeto Conta
Imposto
debitar(20)
creditar
Crédito
saldo
numero
getSaldo
Número
creditar
Crédito
saldo
numero
getSaldo
Número
875,00 21.342-7
854,98 21.342-7
debitar
debitar
875,32
21.342-7
Débito
875,32
21.342-7
Débito
Conta Imposto: Assinatura
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
public class ContaImpostoM extends Conta {
public ContaImpostoM(String numero) {}
}
Conta Imposto: Descrição
public class ContaImpostoM extends Conta {
private static final double taxa = 0.001;
public ContaImpostoM (String numero) {
super (numero);
}
public void debitar(double valor) {
double imposto = (valor * taxa);
super.debitar(valor + imposto);
}
}
Subtipos e Subclasses
ContaImposto
Conta
Subclasses e Comportamento
Objetos da subclasse comportam-se
como os objetos da superclasse
Redefinições de métodos devem
preservar o comportamento
(semântica) do método original
Grande impacto sobre
manutenção/evolução de software...
Revisão/Otimização de
Código
...
double m(Conta c) {
c.creditar(x);
c.debitar(x);
return
c.getSaldo();
}
...
...
double m(Conta c) {
return
c.getSaldo();
}
...
Modificação é correta? Em que contextos?
Subclasses e Evolução de
Software
Deveria ser possível raciocinar sobre
o código usando-se apenas a definição
dos tipos das variáveis envolvidas
(Conta)
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
Poupança
ContaImpostoM
ContaEspecial
Reuso preservando Subtipos
ContaAbstrata
Conta
Poupanca
ContaImposto
ContaEspecial
Definindo Classes Abstratas
public abstract class ContaAbstrata {
private String numero;
private double saldo;
public ContaAbstrata (String numero) {
this.numero = numero;
saldo = 0.0;
}
public void creditar(double valor) {
saldo = saldo + valor;
}
/* ... */
Definindo Classes Abstratas
/* ... */
public abstract void debitar(double valor);
protected void setSaldo(double saldo) {
this.saldo = saldo;
}
public double getSaldo() {
return saldo;
}
/* ... */
}
Classes Abstratas
Possibilita herança de código preservando
comportamento (semântica)
Métodos abstratos:
• Geralmente existe pelo menos um
• São implementados nas subclasses
Não cria-se objetos:
• Mas podem (devem) ter construtores, para reuso
• Métodos qualificados como protected para
serem acessados nas subclasses
Contas: Descrição
Modificada
public class Conta extends ContaAbstrata {
public Conta(String numero) {
super (numero);
}
public void debitar(double valor) {
this.setSaldo(getSaldo() - valor);
}
}
Poupanças: Descrição
Original
public class Poupanca extends Conta {
public Poupanca(String numero) {
super (numero);
}
public void renderJuros(double taxa) {
this.creditar(getSaldo() * taxa);
}
}
Conta Especial: Descrição
Original
public class ContaEspecial extends Conta {
public static final double taxa = 0.01;
private double bonus;
public ContaEspecial (String numero) {
super (n);
}
public void creditar(double valor) {
bonus = bonus + (valor * taxa);
super.creditar(valor);
}
/* ... */
}
Conta Imposto: Descrição
public class ContaImposto extends ContaAbstrata {
public static final double taxa = 0.001;
public ContaImposto (String numero) {
super (numero);
}
public void debitar(double valor) {
double imposto = valor * taxa;
double total = valor + imposto;
this.setSaldo(getSaldo() – total);
}
}
Substituição e Ligações
Dinâmicas
...
ContaAbstrata ca, ca’;
ca = new ContaEspecial(¨21.342-7¨);
ca’ = new ContaImposto(¨21.987-8¨);
ca.debitar(500);
ca’.debitar(500);
System.out.println(ca.getSaldo());
System.out.println(ca’.getSaldo());
...
Classes Abstratas:
Utilização
Herdar código sem quebrar noção de
subtipos, preservando o
comportamento do supertipo
Generalizar código, através da
abstração de detalhes não relevantes
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;
}
/* ... */
}
Cliente: Projeto OO
public abstract class Cliente {
private String nome;
private ConjuntoContato contatos;
/* ... */
public void incluirContato(Contato contato) {
contatos.incluir(contato);
}
public abstract Endereco getEndereco();
public abstract Contato getContato(String tipo);
/* ... */
}
Contato: Reuso e Subtipos
Contato
Endereco
Telefone
EndEletronico EndPostal
Contato: Projeto OO
public abstract class Contato {
private String tipo;
public Contato (String tipo) {
this.tipo = tipo;
}
public abstract String getInfoRotulo();
}
Endereço: Projeto OO
public abstract class Endereco extends Contato {
public Endereco (String tipo) {
super (tipo);
}
}
Endereço Eletrônico: Projeto
OO
public class EnderecoEletronico extends Endereco {
private String email;
public EnderecoEletronico(String email) {
super (“EnderecoEletronico”);
this.email = email;
}
public String getInfoRotulo() {
return (“Email: ” + email);
}
}
Endereço Residencial:
Projeto
public class EnderecoPostal extends
Endereco {
private String rua;
private String cidade;
// ...
public EnderecoPostal(String cidade,
String rua,/*...*/) {
super (“EnderecoPostal”);
this.cidade = cidade;
this.rua = rua;
// ...
}
public String getInfoRotulo() {
return (“Rua: ” + rua /*...*/);
}
}
Telefone: Projeto
public class Telefone extends Contato {
private String ddd;
private String numero;
public Telefone(String ddd, String numero) {
super (“Telefone”);
this.numero = numero;
this.ddd = ddd;
}
public String getInfoRotulo() {
return (“DDD: ” + ddd +
“Numero: “ + numero);
}
}
Auditor de Banco
public class AuditorB {
private final static double MINIMO = 500.00;
private String nome;
/* ... */
public boolean auditarBanco(Banco banco) {
double saldoTotal, saldoMedio;
int numeroContas;
saldoTotal = banco.saldoTotal()
numeroContas = banco.numeroContas();
saldoMedio = saldoTotal/numeroContas;
return (saldoMedio < MINIMO);
}
}
Auditor de Banco Modular
public class AuditorBM {
private final static double MINIMO = 500.00;
private String nome;
/* ... */
public boolean auditarBanco(BancoModular banco)
{
double saldoTotal, saldoMedio;
int numeroContas;
saldoTotal = banco.saldoTotal()
numeroContas = banco.numeroContas();
saldoMedio = saldoTotal/numeroContas;
return (saldoMedio < MINIMO);
}
}
Problema
Duplicação desnecessária de código
O mesmo auditor deveria ser capaz de
investigar qualquer tipo de banco que
possua operações para calcular
• o número de contas, e
• o saldo total de todas as contas.
Auditor Genérico
public class AuditorGenerico {
private final static double MINIMO = 500.00;
private String nome;
/* ... */
public boolean auditarBanco(QualquerBanco banco)
{
double saldoTotal, saldoMedio;
int numeroContas;
saldoTotal = banco.saldoTotal()
numeroContas = banco.numeroContas();
saldoMedio = saldoTotal/numeroContas;
return (saldoMedio < MINIMO);
}
}
Definindo Interfaces
public interface QualquerBanco {
double saldoTotal();
int numContas();
}
Interfaces
Caso especial de classes abstratas...
• todos os métodos são abstratos
—provêem uma interface para serviços e
comportamentos
—são qualificados como public por default
• não definem atributos
—definem constantes
—por default todos os “atributos” definidos
em uma interface são qualificados como
public, static e final
• não definem construtores
Interfaces
Não pode-se criar objetos
Definem tipo de forma abstrata,
apenas indicando a assinatura dos
métodos
Os métodos são implementados pelos
subtipos (subclasses)
Subtipos sem Herança de
Código
public class Banco implements QualquerBanco {
/* ... */
}
public class BancoModular
implements QualquerBanco {
/* ... */
}
implements
classe implements
interface1, interface2, ...
subtipo implements
supertipo1, supertipo2, ...
Múltiplos supertipos:
• uma classe pode implementar mais de
uma interface (contraste com classes
abstratas...)
implements
Classe que implementa uma interface
deve definir os métodos da interface:
• classes concretas têm que implementar
os métodos
• classes abstratas podem simplesmente
conter métodos abstratos
correspondentes aos métodos da
interface
Usando Auditores
Banco b = new Banco();
BancoModular bm = new BancoModular();
Auditor a = new Auditor();
/* ... */
boolean r = a.auditarBanco(b);
boolean r’ = a. auditarBanco(bm);
/* ... */
Interfaces e Reusabilidade
• Evita duplicação de código através da
definição de um tipo genérico, tendo como
subtipos várias classes não relacionadas
• Tipo genérico pode agrupar objetos de
várias classes definidas de forma
independente, sem compartilhar código via
herança, tendo implementações totalmente
diferentes
• Classes podem até ter mesma semântica...
Definição de Classes: Forma
Geral
class C’
extends C
implements I1, I2, ..., In {
/* ... */
}
I1
I2 C
C’
... In
Subtipos com Herança
Múltipla de Assinatura
interface I
extends I1, I2, ..., In {
/*... assinaturas de novos
métodos ...
*/
}
O que usar? Quando?
Classes (abstratas)
Agrupa objetos com
implementações
compartilhadas
Define novas classes
através de herança
(simples) de código
Só uma pode ser
supertipo de outra
classe
Interfaces
Agrupa objetos com
implementações
diferentes
Define novas interfaces
através de herança
(múltipla) de assinaturas
Várias podem ser
supertipo do mesmo tipo
Cadastro de Contas:
Parametrização
public class CadastroContas {
private RepositorioContas contas;
public CadastroContas (RepositorioContas r) {
if (r != null) contas = r;
}
/* ... */
}
A estrutura para armazenamento das contas é
fornecida na inicialização do cadastro,
e pode depois ser trocada!
Repositório: Definição
public interface RepositorioContas {
void inserir(Conta conta);
Conta procurar(String numero);
boolean existe(String numero);
}
Repositório: Implementações
public class ConjuntoContas
implements RepositorioContas {...}
public class ListaContas
implements RepositorioContas {...}
public class ArrayContas
implements RepositorioContas {...}
public class VectorContas
implements RepositorioContas {...}
Cadastro de Contas:
Parametrização
public void cadastrar(Conta conta) {
if (conta != null) {
String numero = conta.getNumero();
if (!contas.existe(numero)) {
contas.inserir(conta);
}
}
}
Cadastro de Contas:
Parametrização
public void debitar(String numero, double valor){
Conta conta;
conta = contas.procurar(numero);
if (conta != null) {
conta.debitar(val);
}
}