Segundo o GoF, "os Decorators fornecem uma alternativa flexível a subclasses para extensão da funcionalidade".
A palavra importante aí é "extensão". Ao se criar um objeto decorador, ele não deverá ter conhecimento da classe que ele vai decorar. Um decorador "barra de rolagem" só deverá saber que ele vai extender a funcionalidade, acrescentando o mecanismo da barra de rolagem. Será utilizado num campo texto? Será utilizado num treeview? Bom, ele não precisa (nem pode) saber!
Por quê eu digo que "nem pode" saber? Porque se ele tiver um conhecimento, uma referência à classe que ele vai decorar, ele vai ficar amarrado a esta classe! Caso você precise agora decorar uma tabela com barra de rolagem, a classe decoradora terá que ser alterada também, para incluir uma nova referência ao objeto tabela. E isso é ruim, muito ruim! Todo novo objeto a ser decorado com barra de rolagem demandará manutenção no código da classe decoradora! Além do mais, isso fere um dos princípios mais básicos das boas práticas da Orientação a Objetos: o Acoplamento, isto é, o grau em que uma classe conhece outra classe. Devemos buscar sempre o Baixo Acoplamento.
Logo, o objeto decorador "barra de rolagem" só precisa saber que ele vai adicionar este mecanismo ao objeto. Precisa de barra de rolagem num campo texto? Basta fazer campo.add(barraDeRolagem); agora eu preciso em uma tabela... Ok, basta fazer tabela.add(barraDeRolagem); o decorador sofreu alguma alteração no código? Absolutamente nenhuma!
Utilizando o exemplo do objeto Pizza que deverá ser decorado com Condimentos, obteremos o seguinte: tanto Pizza quanto Condimentos serão produtos alimentícios, dos quais eu vou querer saber o preço e descrição. Teremos:
public interface ProdutoAlimenticio {
public double getCusto();
public String getDescricao();
}
O meu Decorator também será do gênero produto alimentício (queijo, orégano, etc), logo, vai implementar a interface acima. Se eu pedir a descrição dos condimentos da pizza, precisarei imprimir "Queijo, Orégano, Calabreza, etc", ou seja, sobrescrevo o getDescricao() para concatenar com vírgulas. Logo, teremos:public abstract class CondimentoDecorator
implements ProdutoAlimenticio {
public abstract String getDescricaoDoComplemento();
public String getDescricao() {
return ", " + getDescricaoDoComplemento();
}
}
A classe Pizza deveria implementar a interface ProdutoAlimenticio, porém, vou adicionar uma camada intermediária, entre Pizza e ProdutoAlimenticio, com o intuito de flexibilizar a adição de novos pratos além de Pizza, como por exemplo HotDog. Vou chamar esta classe de Refeição, e veremos na prática como isto vai facilitar o reaproveitamento de código, o que é, aliás, uma das grandes vantagens da Orientação a Objetos.
Portanto, a classe Refeição deverá implementar a interface ProdutoAlimenticio, mas ainda será uma classe abstrata, pois ela ainda não sabe informar o custo da refeição - getCusto() - o que vai ser responsabilidade de suas subclasses, até porque este custo vai ser um para Pizza e outro para HotDog, etc.
Note que na classe Refeição não há qualquer referência a decoradores ou qualquer outro objeto que não interessa a ela. Tudo o que a classe Refeição sabe é que ela é um Produto Alimentício, que refeições têm um custo e uma descrição, e que refeições podem adicionar complementos - que, por sua vez, também serão Produtos Alimentícios. Logo, a classe Refeicao.java fica assim:
public abstract class Refeicao
implements ProdutoAlimenticio{
private String descricao;
private double custo;
protected abstract double getPrecoDaRefeicao();
// Construtor da classe com String como parâmetro
protected Refeicao(String descricao){
this.descricao = descricao;
this.custo = getPrecoDaRefeicao();
}
public double getCusto() {
return custo;
}
public String getDescricao() {
return descricao;
}
public void add(ProdutoAlimenticio complemento) {
custo += complemento.getCusto();
descricao += complemento.getDescricao();
}
}
O que o método add acima faz, é simplesmente incrementar o custo da própria refeição com o custo de seu complemento, e da mesma forma, concatenar sua descrição com a descrição do complemento. Note que este método recebe um ProdutoAlimenticio como parâmetro; é tudo o que a refeição precisa saber - que ela pode adicionar complementos alimentícios. E só!
Agora vamos implementar uma classe concreta de Decorator, por exemplo, Queijo. Você concorda comigo que Queijo poderá decorar tanto Pizzas quanto HotDogs? E que poderá decorar pratos que ainda nem foram criados? Que, se amanhã o cliente inventar um novo prato, e precisar decorá-lo com Queijo, este decorador deverá ser capaz de atendê-lo? Ou seja, está bastante evidente que na classe Queijo NÃO poderá haver referências a pratos, nem a pizzas, nem a refeições. Até porque, na classe CondimentoDecorator.java acima, a regra de concatenar sua descrição com vírgula já foi implementada. Logo, para a classe Queijo.java sobrou apenas informar o seu custo e sua descrição - responsabilidades pertinentes apenas à classe Queijo e à mais ninguém! Portanto, Queijo.java fica apenas assim:public class Queijo extends CondimentoDecorator {
public double getCusto() {
return 5;
}
public String getDescricaoDoComplemento() {
return "Queijo";
}
}
Ou seja, a classe Queijo é um CondimentoDecorator; e Queijo informa seu custo e sua descrição. Você sequer precisa descobrir quais métodos implementar na classe Queijo! Como Queijo extends CondimentoDecorator, a própria compilação te informa que você precisa implementar o método abstrato getDescricaoDoComplemento(); e que você precisa implementar o método abstrato getCusto() , uma vez que CondimentoDecorator não o implementou de ProdutoAlimenticio. Isto é, não tem como errar! É tão fácil adicionar novos condimentos que iremos adicionar mais um: Calabreza.java :public class Calabreza extends CondimentoDecorator {
public double getCusto() {
return 7;
}
public String getDescricaoDoComplemento() {
return "Calabreza";
}
}
Agora vamos implementar a classe Pizza. Adivinha o que sobrou para ser implementado em Pizza? Exato! Apenas a responsabilidade do que é de Pizza, ou seja, assim como Queijo e Calabreza, a classe Pizza só precisa informar seu custo e sua descrição! Aqui eu vou delegar ao construtor da classe Pizza a atribuição da descrição, certo? Só para permitir ao Cliente fazer: Pizza marguerita = new Pizza("Marguerita") ou Pizza simples = new Pizza("Mussarela"), etc. Logo, a classe Pizza.java fica apenas assim:public class Pizza extends Refeicao {
//Construtor atribui a descrição
public Pizza(String descricao) {
super(descricao);
}
public double getPrecoDaRefeicao() {
return 10;
}
}
Da mesma forma, Pizza é uma Refeição. Aqui também a compilação te informa o quê é preciso implementar. Caso você se esqueça de algo (ou algum colega novato na equipe...) será possível identificar o problema em tempo de compilação - o que torna o nosso código muito mais resistente a falhas! Importante perceber também que Pizza.java não tem referência a nenhum decorador. Claro! Nem poderia! Iria ter referência a qual decorador? Queijo? Aqui, da forma como está feito, caso o Cliente crie um novo decorador (orégano, por exemplo), tudo o que eu preciso fazer é criar uma nova classe Oregano.java. Só isso! A classe Pizza.java (nem qualquer outra) não vai sofrer nenhuma alteração.
Bom, vamos testar o projeto numa classe Executavel.java, que contenha um método main():
public class Executavel {
public static void main(String[] args) {
Pizza p = new Pizza("Pizza Completa");
p.add(new Queijo());
p.add(new Calabreza());
System.out.println("Descricao: "+p.getDescricao());
System.out.println("Total: R$ "+p.getCusto());
}
}
Pronto! Tudo o que eu preciso fazer para decorar o objeto Pizza com um novo comportamento é passar o Decorator como parâmetro ao método add(). Note que, como este método precisa de um ProdutoAlimenticio, e Pizza é um ProdutoAlimenticio, eu poderia fazer:
Pizza pizza = new Pizza("Pizza Dupla");
pizza.add(new Pizza(""));
Como exercício, fica pra você a tarefa de criar um novo prato HotDog.java e um novo decorador para o complemento Oregano.java, e acrescentar orégano à Pizza já existente, e também reaproveitar Queijo e Calabreza para adicionar ao HotDog. (Abaixo está a solução)Tudo se resume a isso:
public class HotDog extends Refeicao {
public HotDog(String descricao) {
super(descricao);
}
public double getPrecoDaRefeicao() {
return 9;
}
}
public class Oregano extends CondimentoDecorator {
public double getCusto() {
return 2;
}
public String getDescricaoDoComplemento() {
return "Orégano";
}
}
public class Executavel {
public static void main(String[] args) {
Pizza p = new Pizza("Pizza");
p.add(new Queijo());
p.add(new Calabreza());
p.add(new Oregano());
HotDog dog = new HotDog("Hot Dog");
dog.add(new Queijo());
dog.add(new Calabreza());
System.out.println("Descricao: "+p.getDescricao());
System.out.println("Total: R$ "+p.getCusto());
System.out.println("Descricao: "+dog.getDescricao());
System.out.println("Total: R$ "+dog.getCusto());
}
}
Bem, espero que tenham gostado! Qualquer dúvida deixe uma mensagem que terei o maior prazer em respondê-la! Também escrevam para enviar sugestões. Pretendo colocar exemplos bem explicativos de todos os 23 padrões do GoF, que é um assunto obrigatório para quem quiser programar orientado a objetos, e onde ainda vejo muita deficiência por aí... Abraços.