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); } }