Design Pattern: Implementando o Decorator
Posted by Alberto Leal on June 23rd, 2009
Photo: http://static.howstuffworks.com/gif/teen-bedroom-decorating-ideas-2.jpg
O último livro que terminei de ler era sobre Design Pattern. Então, resolvi fazer alguns posts contendo algumas implementações. O padrão de projeto da vez é o Decorator.
——–
O poder da composição
Têm quem ame, e têm quem odeie herança. Existem casos que não há como fugir da herança, mas, sempre que possível, prefira composição a herança. Quando utilizamos herança em uma parte da nossa aplicação, e alguns comportamentos são herdados, estes são definidos estaticamente em tempo de compilação. Por outro lado, se você estender o comportamento de um objeto por meio de composição, você consegue fazer isso dinamicamente, ou seja, consegue compôr os objetos de forma dinâmica. Com isso, se torna fácil adicionar novas funcionalidades escrevendo um código novo ao invés de alterar o código existente. Isso é bacana, pois o comportamento anterior não é modificado, assim as chances de introdução de erros no código que já estava funcionando é menor. Se algo der errado, basta remover a nova classe que foi criada, por exemplo. Resumindo, mantenha as classes abertas para extensão e fechadas para alteração.
O padrão de projeto decorator anexa novas responsabilidades em um objeto dinamicamente, utilizando do poder da composição.
Vamos ver como isso funciona na prática. O exemplo que lhe será apresentado é a construção de uma pizza. No momento do pedido, o cliente poderá pedir ao Pizzaiolo que adicione novos ingredientes na pizza, tais como: orégano e tomate. Exemplo simples e direto, apenas para ilustrar.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | package br.eti.albertoleal.decorator; public abstract class Pizza { private String description = "Unknown Pizza"; public void setDescription(String desc) { description = desc; } public String getDescription(){ return description; } public abstract double cost(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package br.eti.albertoleal.decorator; public class Cheese extends Pizza { public Cheese(){ setDescription("Cheese Pizza") } @Override public double cost() { return 19.90; } } |
As classes decorator devem ser espelhos das classes que elas vão decorar. Vamos utilizar da herança em nossos objetos do tipo CondimentDecorator. O motivo de se usar herança na classe Decorator é pelo simples fato de se ter o mesmo tipo dos objetos que vão ser decorados. O uso de herança é justamente para atingir essa correspondência de tipo.
1 2 3 4 5 | package br.eti.albertoleal.decorator; public abstract class CondimentDecorator extends Pizza { } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | package br.eti.albertoleal.decorator; public class Oregano extends CondimentDecorator { Pizza pizza; public Oregano(Pizza pz) { pizza = pz; } @Override public String getDescription() { return pizza.getDescription() + ", Oregano"; } @Override public double cost() { return .50 + pizza.cost(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | package br.eti.albertoleal.decorator; public class Tomato extends CondimentDecorator { Pizza pizza; public Tomato(Pizza pz){ pizza = pz; } @Override public String getDescription() { return pizza.getDescription() + ", Tomato "; } @Override public double cost() { return .10 + pizza.cost(); } } |
Se você estava atento reparou que a cada condimento que é adicionado na pizza um valor correspondente ao condimento é somado ao valor da pizza. Essa é a idéia do decorator. Estou decorando um objeto pizza com diversos outros componentes. Um outro exemplo, bastante comum de se encontrar pela internet, são as janelas gráficas. O ato de decorar uma janela gráfica com diversos componentes de apresentação, tais como: barra de rolagem, campos de texto e por aí vai.
Chegou a hora de testarmos o nosso código, criar uma pizza e adicionar alguns condimentos a ela e constatar se a nossa “decoração” funciona:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | package br.eti.albertoleal.decorator; public class Pizzaiolo { public static void main(String[] args) { Pizza pz = new Cheese(); pz = new Tomato(pz); pz = new Tomato(pz); pz = new Oregano(pz); pz = new Tomato(pz); pz = new Oregano(pz); pz = new Tomato(pz); System.out.println(pz.getDescription()); } } |
Concluindo:
- A herança é uma maneira de estender comportamentos, mas não é a melhor maneira de obter flexibilidade;
- Composição e delegação são boas maneiras de se adicionar funcionalidades em tempo de execução;
- A utilização de um componente decorator é simplesmente decorar um objeto, e o objeto decorado não precisa conhecer os decoradores;
- É muito importante que os objetos decoradores sejam espelhos do objeto que vão decorar, justamente para ter uma correspondência de tipo.
Recent Comments