Design Pattern: Implementando o Decorator
Engenharia de SW, Java, Livros 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.
June 23rd, 2009 at 9:21 am
Interessante e ótimo artigo!
Faltam artigos bons em sites/blogs sobre padrões de projeto…
Qual livro você leu? Eu estou relendo o livro tradicional do GoF, e a cada linha, um conhecimento que vale infinitamente mais que o preçodo livro! Também li o Use a Cabeça, mas o Lexi do GoF é perfeito para a explicação dos padrões!
Ótimo artigo!!!
June 23rd, 2009 at 9:24 am
Olá William, mto obrigado! É legal ter feedback para estar sempre melhorando a qualidade dos próximos posts.
O livro que foi citado é o Head First.
Abs!
June 23rd, 2009 at 9:40 am
Esse livro é ótimo! Li ele até a metade, não tive a oportunidade de terminar!
A propósito:
http://www.javafree.org/noticia/4025/Conheca-o-padrao-de-projeto-Decorator.html
[]’s
August 13th, 2009 at 1:22 pm
Ótimo artigo,
direto ao ponto, sem muitas delongas.
November 25th, 2009 at 8:48 am
Retirou o exemplo do livro \\"Head\\\’s First, Design Patterns\\"..muito bom (o livro), e seu exemplo ta com correçao de um bug que a classe \\"condimentDecorator\\" tinha (ao menos no meu tem), pois quando fui transcrever pra C# houve erros pra calculo de custo.
[]s
November 25th, 2009 at 9:08 am
Foi este livro mesmo que eu li. =)
January 20th, 2010 at 3:12 pm
Muito bom. Mas, vc concorda comigo que a classe CondimentDecorator é “desnecessária” (minha opinião, posso estar errrado…).
Poderíamos eliminá-la e fazer com que a classe \’Oregano\’ herdasse diretamente a pizza e a nomearia como OreganoDecorator.
O mesmo faria com a classe Tomato que passaria a se chamar TomatoDecorator.
O que vc acha?
January 24th, 2010 at 7:04 pm
Ola, adorei seu artigo perfeito mais deixo uma pergunta, se eu seguir tais afirmções não vou estar quebrando regras da OO, muitas vezes utilizo o mesmo pesamento e cabo pensando que estou fazendo errado….
Atenciosamente
Tiago Xavier
Analista de Sistemas
January 24th, 2010 at 7:24 pm
Olá Tiago, quais regras você se refere?
January 24th, 2010 at 7:32 pm
Olá Marcos,
neste caso não podemos fazer isso. Oregano não é um tipo de Pizza.
Herança é o mecanismo pelo qual uma classe pode estender outra classe, aproveitando seus comportamentos e propriedades. Mas para que isso ocorra, é necessário que exista uma relaçao “é um” entre as classes. E, neste caso, Oregano não é uma Pizza.
Um exemplo para herança, seria: Calabresa extends Pizza.
Abs,
Alberto
January 24th, 2010 at 10:15 pm
Talvez o termos \’regra\’ não seja o caso, mais o propósito, em si, não utilização da herança resultaria em redundância.
Eu até utilizo o mesmo conceito em alguns casos, substituo herança por composição.
Mais como utilizar as mesmas sem quebrar os princípios. Na visão do código é quase a mesma coisa, mais se tratando de uma análise.
Composição é um objeto composto por um todo de outro objeto,
já herança um objeto herda as semelhanças de outro objeto, são conceitos totalmente diferentes na analise.
Cara, parabéns pelo artigo estou até pensando em abrir um blogs ou algo assim,para mim para trocar experiências.
January 25th, 2010 at 9:10 am
É verdade Alberto (\"é necessário que exista uma relaçao \’é um\’ entre as classes. E, neste caso, Oregano não é uma Pizza\"). A questão é mais conceitual. Mas, vc acha que CondimentDecorator é uma Pizza? Na minha opinião, o condimento não é do tipo pizza. Uma pizza contém condimentos…
Parabéns pelo artigo (Só estou comentando para fixar mais o assunto, Obrigado).
January 25th, 2010 at 9:25 am
Entendi o que você quer dizer. Neste caso, foi utilizado herança para manter a correspondência de tipos, de modo que se tenha o mesmo tipo dos objetos que serão decorados.
Poderíamos utilizar uma interface “Pizza” e fazer com que, tanto a classe Decorator quanto as classes concretas (Cheese, por ex) implementem essa interface. Isso, também, para manter a correspondência de tipos.
Abraços
March 5th, 2010 at 8:51 am
Uma dúvida: a implementação e utilização do padrão Decorator é sempre baseada em construtores? Achei pouco flexível ter que criar uma nova instância de objeto toda vez que se quiser adicionar uma nova funcionalidade. Por exemplo, se o objeto pizza1 estiver em uma Collection e eu quiser adicionar “manjericão” na pizza1, eu teria que remover a instância pizza1 da collection, criar uma nova instância de Pizza (pizza2) com manjericão e adicionar pizza2 na collection. Não seria melhor se pudesse fazer pizza1.addCondiment(manjericao) do que pizza2 = new Manjericao(pizza1)? Voltando a minha pergunta, teria como fazer isso usando o Decorator, ou o padrão é baseado sempre em construtores?
March 5th, 2010 at 8:51 am
Uma dúvida: a implementação e utilização do padrão Decorator é sempre baseada em construtores? Achei pouco flexível ter que criar uma nova instância de objeto toda vez que se quiser adicionar uma nova funcionalidade. Por exemplo, se o objeto pizza1 estiver em uma Collection e eu quiser adicionar “manjericão” na pizza1, eu teria que remover a instância pizza1 da collection, criar uma nova instância de Pizza (pizza2) com manjericão e adicionar pizza2 na collection. Não seria melhor se pudesse fazer pizza1.addCondiment(manjericao) do que pizza2 = new Manjericao(pizza1)? Voltando a minha pergunta, teria como fazer isso usando o Decorator, ou o padrão é baseado sempre em construtores?
March 5th, 2010 at 9:51 am
Alan, você não cria uma instância de Pizza toda hora que vc deseja decorá-la. Você cria uma nova instância de condimento. Com isso, não seria necessário criar um novo objeto Pizza apenas para adicionar Manjericão. Você pode utilizar pizza1 que já se encontra na sua collection, sem nenhum problema. A maneira como você implementa um padrão de projeto não necessariamente deve seguir a risca o modelo como foi catalogado. É apenas um conceito e uma maneira de resolução. O que quero dizer é que você pode tranquilamente fazer adaptações para a sua realidade.
May 26th, 2010 at 9:30 pm
Alberto, meus parabéns pela iniciativa! Muito importante este assunto. Só queria acrescentar que, da mesma forma como disse o colega Marcos, eu também discordo que CondimentDecorator seja uma Pizza! Mais do que um erro meramente conceitual, isso acarretará em uma falha grave, veja se concorda comigo: tanto a classe Tomato como a classe Oregano vão ter uma referência para Pizza. É como dizer que o decorador “Barra de Rolagem” precisa conhecer a classe da Caixa de Texto que ele vai decorar - e isso é falso! Imagine agora que eu tenho HotDog além de Pizza, e meu hotDog também leva Tomate, mas como vou usar o Decorator Tomate para usar em HotDog? Precisarei alterar o Decorator para suportar novos tipos? Percebe o problema? Enfim, eu abordei a questão em http://marconems.blogspot.com/2010/05/decorator.html se puder conferir, acho que iria esclarecer. Espero ter contribuído para o aprendizado de todos, inclusive o meu! Grande abraço!