UNIFEI Disciplina Professor Universidade Federal de Itajubá Instituto de Engenharia de Sistemas e Tecnologias da Informação-IESTI CCO002 – Engenharia de Software Enzo Seraphim 1 Padrões de Construção A maneira padrão de construir objetos em uma linguagem orientada à objetos é através de construtores. Toda classe tem um construtor: operação declarada com o mesmo nome da classe, que não retorna valor e só pode ser usada na inicialização. Se um construtor não é explicitamente declarado em uma classe, o sistema cria um construtor default para a classe. O construtor é responsável por criar uma instância de seu objeto e alocar qualquer memória ou inicializar qualquer campo necessário de forma que o objeto esteja em estado de uso depois da saída do construtor. Portanto, o construtor é uma oportunidade de inicializar de forma organizada as características dos objetos. Os métodos construtores são definidos definindo um método com o mesmo nome da classe. Toda classe pode ter um ou mais construtores, mas apenas um deve ser executado para cria o objeto. Dependendo do tipo do objeto, o construtor pode ter diferentes números de parâmetros. Para que uma classe seja instanciada em java deve ser declarado um construtor. Todo construtor inicializa a hierarquia de classes do objeto antes de executar e sempre contém ou uma referência à superclasse (implícita ou explícita) ou uma referência a outro construtor da classe como primeira ou única instrução A definição da classe coisa, “public class Coisa {}” é igualmente declarada usando a sintaxe abaixo: 01: 02: 03: 04: 05: public class Coisa extends java.lang.Object { public Coisa() { super(); } } O compilador insere o construtor, a super classe e a chamada para construtor padrão mesmo se forem omitidos na declaração. O exemplo abaixo define uma classe chamada coisa com um membro nome. 01: 02: 03: 04: 05: 06: public class Coisa { private String nome = "Ainda Sem Nome"; public Coisa(String nome) { this.nome = nome; } } O exemplo abaixo define uma classe OutraCoisa que herda os membros de Coisa. Note que o construtor dessa classe chama o construtor da super classe. 01: 02: 03: 04: 05: 06: public class OutraCoisa extends Coisa{ private int idade = 10; public OutraCoisa() { super("Outra Coisa"); } } Existem 3 problemas para utilização de construtores: cliente pode não ter todos os dados necessários para instanciar um objeto; cliente fica acoplado a uma implementação concreta (precisa saber a classe concreta para usar new com o construtor); cliente de herança pode criar construtor que chama métodos que dependem de valores ainda não inicializados. Os padrões que oferecem alternativas à construção de objetos: ● Builder: obtém informação necessária em passos antes de requisitar a construção de um objeto ● Factory Method: adia a decisão sobre qual classe concreta instanciar ● Abstract Factory: construir uma família de objetos que compartilham um "tema" em comum ● Prototype: especificar a criação de um objeto a partir de um exemplo fornecido ● Memento: reconstruir um objeto a partir de uma versão que contém apenas seu estado interno 1.1 Construtor - Builder "Separar a construção de um objeto complexo de sua representação para que o mesmo processo de construção possa criar representações diferentes." [GoF] Estrutura Exemplo Aplicação ● ● ● ● Builder permite que uma classe se preocupe com apenas uma parte da construção de um objeto. É útil em algoritmos de construção complexos . Use-o quando o algoritmo para criar um objeto complexo precisar ser independente das partes que compõem o objeto e da forma como o objeto é construído. Builder também suporta substituição dos construtores, permitindo que a mesma interface seja usada para construir representações diferentes dos mesmos dados. Use quando o processo de construção precisar suportar representações diferentes do objeto que está sendo construído. Exemplo Java public abstract class Movel{ private String material; public String getMaterial(){return material;}; public void setMaterial(String v){material=v;}; } public class Cama extends Movel{ private int tipo; public int getTipo(){return tipo;} public void setTipo(int v){tipo=v;} } public class GuardaRoupa extends Movel{ private int porta; public int getPorta(){return porta;} public void setPorta(int v){porta=v;} } import java.util.*; public class Quarto{ List <Movel> movel = new ArrayList<Movel>(); public Quarto() { movel.add(new Cama()); movel.add(new GuardaRoupa()); } public Movel getMovel(int id){ if (movel.size()>id) return movel.get(id); else return null; } } 1.2 Método Fábrica - Factory Method "Definir uma interface para criar um objeto mas deixar que subclasses decidam que classe instanciar. Factory Method permite que uma classe delegue a responsabilidade de instanciamento às subclasses." [GoF] Problema ● É possível criar um objeto sem ter conhecimento algum de sua classe concreta? ● Esse conhecimento deve estar em alguma parte do sistema, mas não precisa estar no cliente ● FactoryMethod define uma interface comum para criar objetos ● O objeto específico é determinado nas diferentes implementações dessa interface ● O cliente do FactoryMethod precisa saber sobre implementações concretas do objeto criador do produto desejado Estrutura Exemplo Exemplo usando classe genérica Vantagens ● ● ● Criação de objetos é desacoplada do conhecimento do tipo concreto do objeto Conecta hierarquias de classe paralelas Facilita a extensibilidade Exemplo Java public abstract class Roupa { private int tamanho; public int getTamanho(){return tamanho;} public void setTamanho(int v){tamanho=v;} } public class Camisa extends Roupa{ private boolean mangaCurta; public boolean getMangaCurta(){return mangaCurta;} public void getMangaCurta(boolean v){mangaCurta=v;} } public class Calca extends Roupa{ private boolean frio; public boolean getFrio(){return frio;} public void getFrio(boolean v){frio=v;} } public abstract class Criador { public abstract Roupa fabricaRoupa(); } public class Costureira extends Criador { private String nome; public String getNome(){return nome;} public void setNome(String v){nome=v;} public Roupa fabricaRoupa() {return new Calca();} } public class Confeccao extends Criador{ private String modeloMaq; public String getModelMaq(){return modeloMaq;} public void setModelMaq(String v){modeloMaq=v;} public Roupa fabricaRoupa() {return new Camisa();} } 1.3 Fábrica Abstrata - Abstract Factory "Prover uma interface para criar famílias de objetos relacionados ou dependentes sem especificar suas classes concretas." [GoF] Estrutura Exemplo Aplicabilidade Este padrão deve ser utilizado quando o programa deve ser configurado por famílias de classes. Todas as classes de uma mesma família devem ser utilizadas em conjunto. Consequências ● Fábrica abstrata permite a troca de toda uma coleção de classes sem grandes modificações no programa fonte; ● As classes concretas são conhecidas apenas pela fábrica abstrata; ● É difícil colocar classes novas no programa. É necessário acrescentar métodos na classe fábrica abstrata e em todas as subclasses; ● As classes dos produtos devem ter uma interface comum. Talvez seja necessário restringi-las para conseguir este objetivo. Exemplo Java public abstract class Anel { } public class AnelBijuteria extends Anel{ private int tipoPedra; private int qtdPedra; public int getTipoPedra(){return tipoPedra;}; public void setTipoPedra(int v){tipoPedra=v;}; public int getQtdPedra(){return qtdPedra;}; public void setQtdPedra(int v){qtdPedra=v;}; } public class AnelOuro extends Anel{ private int tipoPreciosa; private int qtdPreciosa; public int getTipoPreciosa(){return tipoPreciosa;}; public void setTipoPreciosa(int v){tipoPreciosa=v;}; public int getQtdPreciosa(){return qtdPreciosa;}; public void setQtdPreciosa(int v){qtdPreciosa=v;}; } public abstract class Colar { private int qtdPreciosa; public int getQtdPreciosa(){return qtdPreciosa;} public void setQtdPreciosa(int v){qtdPreciosa=v;} } public class ColarBijuteria extends Colar{ private int tipoMaterial; public int getTipoMaterial(){return tipoMaterial;} public void setTipoMaterial(int v){tipoMaterial=v;} } public class ColarOuro extends Colar{ private int quilate; public int getQuilate(){return quilate;} public void setQuilate(int v){quilate=v;} } public abstract class Joalheiro { public abstract Colar novoColar(); public abstract Anel novoAnel(); } public class JoalheiroBijuteria extends Joalheiro{ public Colar novoColar() {return new ColarBijuteria();} public Anel novoAnel() {return new AnelBijuteria();} } public class JoalheiroOuro extends Joalheiro{ public Colar novoColar() {return new ColarOuro();} public Anel novoAnel() {return new AnelOuro();} } 1.4 Protótipo - Prototype "Especificar os tipos de objetos a serem criados usando uma instância como protótipo e criar novos objetos ao copiar este protótipo." [GoF] Estrutura Exemplo Exemplo usando Curiously Recurring Template Pattern (CRTP) Exemplo Java public abstract class Midia { protected String titulo; public String getTitulo(){return titulo;} public void setTitulo(String v){titulo=v;} public abstract Midia clone(); } public class Conserto { private Midia midia; public Midia getMidia(){return midia;} public void setMidia(Midia v){midia=v.clone();} } public class Video extends Midia{ private String produtora; private String diretor; public String getProdutora(){return produtora;} public void setProdutora(String v){produtora=v;} public String getDiretor(){return diretor;} public void setDiretor(String v){diretor=v;} public Midia clone() { Video v = new Video(); v.setTitulo(titulo); v.setProdutora(produtora); v.setDiretor(diretor); return v; } } public class Jogo extends Midia{ private String fabricante; private int console; public String getFabricante(){return fabricante;} public void setFabricante(String v){fabricante=v;} public int getConsole(){return console;} public void setConsole(int v){console=v;} public Midia clone() { Jogo j = new Jogo(); j.setTitulo(titulo); j.setFabricante(fabricante); j.setConsole(console); return j; } } public class Musical extends Midia{ private String autor; private String produtora; public String getAutor(){return autor;} public void setAutor(String v){autor=v;} public String getProdutora(){return produtora;} public void setProdutora(String v){produtora=v;} public Midia clone() { Musical m = new Musical(); m.setTitulo(titulo); m.setAutor(autor); m.setProdutora(produtora); return m; } } Conseqüências ● padrão Prototype permite que um cliente crie novos objetos ao copiar objetos existentes ● padrão protótipo esconde as classes dos objetos das sub-classes para os clientes; ● Object.clone() pode ser usado como implementação do Prototype pattern em Java mas é preciso lembrar que ele só faz cópias rasas: é preciso copiar também cada objeto membro e seus campos recursivamente. Vantagem ● Criar objetos deste modo é poder aproveitar o estado existente de um objeto 1.5 Recordador - Memento "Sem violar o encapsulamento, capturar e externalizar o estado interno de um objeto para que o objeto possa ter esse estado restaurado posteriormente." [GoF] Estrutura O padrão memento é utilizado para armazenar e restaurar futuramente o estado de um objeto.. Aplicabilidade ● Um snapshot do (parte do) estado de um objeto precisa ser armazenada para que ele possa ser restaurado ao seu estado original posteriormente ● Uma interface direta para se obter esse estado iria expor detalhes de implementação e quebrar o encapsulamento do objeto ● Recordador permite capturar o estado de um objeto para que seja possível recuperá-lo posteriormente ● O meio de armazenamento utilizado depende de quando o objeto terá que ser recuperado e dos riscos envolvidos na não recuperação ● A aplicação mais comum de memento é o suporte a operações de Undo. ● Um memento é um pequeno repositório para guardar estado dos objetos ● Pode-se usar outro objeto, um string, um arquivo ● Recordador guarda um snapshot no estado interno de outro objeto - a Fonte ● Um mecanismo irá requisitar um memento da fonte quando ele necessitar verificar o estado desse objeto ● A fonte reinicializa o memento com informações que caracterizam seu estado atual ● Só a fonte tem permissão para recuperar informãções do memento (o memento é "opaco" aos outros objetos) Exemplo Consequências ● não violam a proteção de informação; ● podem usar muita memória. Exemplo Java public class Endereco { private String logradouro; public Endereco(String v){logradouro=v;} public String getLogradouro() {return logradouro;} public void setLogradouro(String v) {logradouro=v;} } import java.util.*; public class Historico { private List<Endereco> end=new ArrayList<Endereco>(); public int size() {return end.size();} public void add(Endereco e) {end.add(e);} public Endereco get(int id) {return end.get(id);} } public class Cliente { private Historico histo=new Historico(); private String nome; private String logradouro; private int pos=0; private void setRecordador(Endereco v) {logradouro=v.getLogradouro();} public String getNome(){return nome;} public void getNome(String v){nome=v;} public String getLogradouro(){return logradouro;} public void getLogradouro(String v){logradouro=v;} public void createEndereco() { histo.add(new Endereco(logradouro)); pos=histo.size(); } public boolean desfazerEnd(){ if(pos>0){ pos--; setRecordador(histo.get(pos-1)); return true; }else return false; } public boolean refazerEnd(){ if(pos<histo.size()){ pos++; setRecordador(histo.get(pos-1)); return true; }else return false; } }