Dinamicamente, agregar responsabilidades adicionais a um objeto.
Os Decorators fornecem uma alternativa flexível ao uso de subclasses para extensão de funcionalidades.
Wrapper
Algumas vezes queremos acrescentar responsabilidades a objetos individuais, e não a toda uma classe.
Uma forma de adicionar responsabilidades é a herança. Contudo, essa abordagem é inflexível, porque a escolha é feita estaticamente. Clientes não podem controlar como e quando decorar o componente.
Uma abordagem mais flexível é embutir o componente em outro objeto. O objeto que envolve o primeiro é chamado de decorator. O decorator segue a interface do componente que decora, de modo que sua presença é transparente para clientes do componente.
O decorator repassa solicitações para o componente, podendo executar ações adicionais antes ou depois do repasse. A transparência permite encaixar decoradores recursivamente, desta forma permitindo um número ilimitado de responsabilidades adicionais.
Para acrescentar responsabilidades a objetos individuais de forma dinâmica e transparente, ou seja, sem afetar outros objetos;
Para responsabilidades que podem ser removidas;
Quando a extensão através do uso de subclasses não é prática. Às vezes, um grande número de extensões independentes é possível e isso poderia produzir uma explosão de subclasses para suportar cada combinação. Ou a definição de uma classe pode estar oculta ou não estar disponível para a utilização de subclasses (sem transparência);
EXTRA: Quando é impossível estender o comportamento de um objeto usando herança. Muitas linguagens de programação tem a palavra chave final que pode ser usada para prevenir a extensão de uma classe. Para uma classe final, a única maneira de reutilizar seu comportamento existente seria envolver a classe com seu próprio invólucro usando o padrão Decorator.
Metamodelo original Decorator
Modelo de aplicação Decorator
Component: define a interface de comunicação para objetos que podem ter responsabilidades acrescentadas aos mesmos dinamicamente.
ConcreteComponent: define um objeto para o qual responsabilidades adicionais podem ser atribuídas (precisa de decoradores).
Decorator: mantém uma referência para um objeto Component e define uma interface que segue a interface de Component.
ConcreteDecorator: acrescenta responsabilidades ao componente (atributos ou operações/métodos).
Decorator repassa solicitações para o seu objeto Component.
Opcionalmente, ele pode executar operações adicionais antes e depois de repassar a solicitação.
public abstract class Decorator extends Component {
Component component;
}
public class ConcreteComponent extends Component {
public ConcreteComponent() {
}
public void operation() {
System.out.print("Componente");
}
}
public abstract class Component {
public abstract void operation();
}
public class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
this.component = component;
}
public void operation() {
component.operation();
System.out.println(" + Decoração A ");
}
}
public class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
this.component = component;
}
public void operation() {
component.operation();
System.out.println(" + Decoração B ");
}
}
Maior flexibilidade do que a herança estática. O padrão Decorator fornece uma maneira mais flexível de acrescentar responsabilidades a objetos do que pode ser feito com herança estática (múltipla). Com o uso de decoradores, responsabilidades podem ser acrescentadas e removidas em tempo de execução simplesmente associando-as e dissociando-as de um objeto. Em comparação, a herança requer a criação de uma nova classe para cada responsabilidade adicional. Isso dá origem a muitas classes e aumenta a complexidade de um sistema. Além do mais, fornecer diferentes classes Decorator para uma específica classe Component permite combinar e associar responsabilidades. Os Decorators também tornam fácil acrescentar uma propriedade duas vezes, enquanto herdar de uma classe duas vezes é um procedimento sujeito a erros, na melhor das hipóteses.
Evita classes sobrecarregadas de características na parte superior da hierarquia. Um Decorator oferece uma abordagem do tipo “use quando for necessário” para adição de responsabilidades. Em vez de tentar suportar todas as características previsíveis em uma classe complexa e customizada, você pode definir uma classe simples e acrescentar funcionalidade de modo incremental com objetos Decorator. A funcionalidade necessária pode ser composta a partir de peças simples. Como resultado, uma aplicação não necessita incorrer no custo de características e recursos que não usa. Também é fácil definir novas espécies de Decorators independentemente das classes de objetos que eles estendem, mesmo no caso de extensões não-previstas. Estender uma classe complexa tende a expor detalhes não-relacionados com as responsabilidades que você está adicionando.
Um decorador e o seu componente não são idênticos. Um decorador funciona como um envoltório transparente. Porém, do ponto de vista da identidade de um objeto, um componente decorado não é idêntico ao próprio componente. Daí não poder depender da identidade de objetos quando você utiliza decoradores (perda de identidade).
Grande quantidade de pequenos objetos. Um projeto que usa o Decorator frequentemente resulta em sistemas compostos por uma grande quantidade de pequenos objetos parecidos. Os objetos diferem somente na maneira como são interconectados, e não nas suas classes ou no valor de suas variáveis. Embora esses sistemas sejam fáceis de customizar por quem os compreende, podem ser difíceis de aprender e depurar.
EXTRA:
I: É difícil remover uma decoração.
II: É difícil implementar uma decoradora de tal maneira que seu comportamento não dependa da ordem da pilha de decorações.
Adapter: Um padrão Decorator é diferente de um padrão Adapter no sentido de que um Decorator somente muda as responsabilidades de um objeto, não a sua interface; já um Adapter dará a um objeto uma interface completamente nova.
Composite: Um padrão Decorator pode ser visto como um padrão Composite degenerado com somente um componente. Contudo, um Decorator acrescenta responsabilidades adicionais – ele não se destina a agregação de objetos.
Strategy : Um padrão Decorator permite mudar a superfície de um objeto, um padrão Strategy permite mudar o seu interior. Portanto, essas são duas maneiras alternativas de mudar um objeto. Veja o Artigo Extra I. Strategy e Decorator .
As APIs da linguagem Java estão repletas de exemplos de implementação dos padrões Proxy e Decorator. Como todo bom encapsulamento, se você já utilizou algum deles, provavelmente não percebeu a sua presença.
Por exemplo, a API de coleções possui algumas classes que servem para “decorar” as coleções existentes adicionando funcionalidades a elas. A classe Collections possui métodos estáticos como synchronizedList(List<T> list) que retorna a lista recebida com sincronização e unmodifiableList(List<? extends T> list) para criar uma lista não-modifcável. O que na verdade esses métodos fazem é encapsular a lista passada em um decorador que adiciona essa característica ao objeto retornado.
O padrão Decorator é um padrão de design estrutural que permite adicionar novos comportamentos e funcionalidades a um objeto existente de forma dinâmica, sem alterar a sua estrutura básica.
O padrão envolve a criação de uma classe decoradora que envolve a classe original e adiciona novos comportamentos.
Este padrão é útil quando você tem um conjunto de classes que precisam ter comportamentos adicionais que podem ser compostos de diferentes maneiras.
O código cliente deve ser responsável por criar decorações e compo-las do jeito que o cliente precisa.
O padrão Decorator não é baseado em refatoração, mas sim na identificação de um conceito que precisa ser ampliado pela adição de novas funcionalidades, ou seja, pela adição de novo código.
Desta forma, não utiliza nenhuma técnica básica de refatoração em sua construção.
Para aplicar o padrão de projeto Decorator em um projeto, você pode seguir os seguintes passos:
Identificar o conceito base (Component): comece identificando a classe que representa a base que precisa ser decorada. Essa classe deve ser implementada como uma interface ou uma classe abstrata que define o comportamento básico a ser adicionado.
Criar um conceito decorador (Decorator): crie uma classe abstrata ou interface decoradora que tenha o mesmo tipo ou assinatura da classe do passo 1. A decoradora pode conter métodos concretos junto com os métodos abstratos de decoração.
Criar conceitos específicos (ConcreteComponent): crie uma ou mais classes que estendem ou implementem a classe componente base. Essas classes pode ser usadas para implementar características e comportamentos específicos do conceito.
Criar decoradoras específicas (ConcreteDecorator): crie uma ou mais classes que estendem ou implementem a decoradora e adicionam comportamento adicional específico. Métodos abstratos da decoradora devem ser implementados nestas classses.
Testar o funcionamento do padrão a partir da criação e utilização de objetos decorados, verificando diferentes tipos de combinações de decoração em tempo de execução.
Refatorar o código, se necessário, para garantir que o padrão Decorator esteja sendo aplicado corretamente e de forma clara, sem códigos duplicados dsncesariamente.
Arquivo Carro.java
// Padrão Decorator - Classe Component (Passo 1)
public abstract class Carro {
protected String descricao = "";
protected double preco = 0;
public abstract String getDescricao();
public abstract double getPreco();
}
Arquivo Equipamento.java
// Padrão Decorator - Classe Decorator (Passo 2)
public abstract class Equipamento extends Carro {
protected Carro carro;
public abstract String getDescricao();
public abstract double getPreco();
}
Arquivo CarroSedan.java
// Padrão Decorator - Classe ConcreteComponent (Passo 3)
public class CarroSedan extends Carro {
public String getDescricao() {
return descricao + "Carro Sedan";
}
public double getPreco() {
return preco + 50000.00;
}
}
Arquivo RodasLigaLeve.java
// Padrão Decorator - Classe ConcreteDecorator (Passo 4)
public class RodasLigaLeve extends Equipamento {
public RodasLigaLeve(Carro carro) {
this.carro = carro;
}
public String getDescricao() {
return carro.getDescricao() + ", Rodas de Liga Leve";
}
public double getPreco() {
return carro.getPreco() + 2500.0;
}
}
Arquivo TetoSolar.java
// Padrão Decorator - Classe ConcreteDecorator (Passo 4)
public class TetoSolar extends Equipamento {
public TetoSolar(Carro carro) {
this.carro = carro;
}
public String getDescricao() {
return carro.getDescricao() + ", Teto Solar";
}
public double getPreco() {
return carro.getPreco() + 15000.0;
}
}
Arquivo Main.java
//Passo 5
class Main {
public static void main(String[] args) {
Carro carro = new CarroSedan();
carro = new RodasLigaLeve(carro);
carro = new TetoSolar(carro);
System.out.println(carro.getDescricao() + ": " + carro.getPreco());
// saída: Carro Sedan, Rodas de Liga Leve, Teto Solar: 28000.0
}
}
Para remover as decorações de um objeto é preciso fazer a remoção de cada decoração por vez, seguindo a ordem da pilha de decorações.
O processo para remover uma decoração inclui:
1) o casting do objeto de ConcreteComponent para Decorator;
2) o acesso ao atributo de referência à Component em Decorator - que representa o objeto original antes da aplicação da decoração; e
3) a atribuição do valor a um novo objeto do tipo Component.
Na aplicação do exemplo acima, a remoção de uma decoração poderia ser feita via: Carro carroSemEquipamento = ((Equipamento)carro).carro;