O padrão Decorator é um padrão de design estrutural que tem como objetivo principal adicionar funcionalidades a objetos de maneira dinâmica e flexível. Ele oferece uma alternativa flexível à criação de subclasses para extensão de funcionalidades. O Decorator resolve problemas como:
-
Extensibilidade Dinâmica: Em muitos cenários, é necessário estender as funcionalidades de um objeto sem alterar o código existente. O Decorator permite adicionar novos comportamentos a um objeto em tempo de execução==, sem afetar outros objetos da mesma classe.
-
Evitar Subclasses Excessivas: Em sistemas onde novas funcionalidades podem ser adicionadas de várias maneiras combinadas, a criação de subclasses para cada combinação possível se torna inviável. O Decorator permite combinar funcionalidades de maneira flexível.
-
Responsabilidade Única: Decorators permitem que cada classe ou componente se concentre em uma única responsabilidade ou funcionalidade, aderindo ao princípio da responsabilidade única.
-
Reutilização de Funcionalidades: O Decorator promove a reutilização de funcionalidades, pois permite aplicar um comportamento adicional a diferentes classes sem duplicação de código.
Como Funciona
O padrão Decorator geralmente envolve os seguintes componentes:
- Componente (Interface ou Classe Abstrata): Define a interface para objetos que podem ter responsabilidades adicionais atribuídas dinamicamente.
- Componente Concreto: Define um objeto ao qual responsabilidades adicionais podem ser atribuídas.
- Decorator: Mantém uma referência a um objeto Componente e define uma interface que está em conformidade com a interface do Componente.
- Decoradores Concretos: São classes que estendem o Decorator e adicionam funcionalidades específicas.
Exemplo
Um exemplo comum do uso do Decorator é em sistemas de interface gráfica, onde você pode querer adicionar funcionalidades como bordas, sombras ou rolagem a componentes de interface do usuário (como botões ou painéis) de maneira dinâmica. Outro exemplo é em bibliotecas de I/O, onde diferentes capacidades (como buffering, compressão, criptografia) podem ser adicionadas a fluxos de dados.
Exemplo de implementação
Vamos criar um exemplo simples do padrão Decorator em Java. Imaginemos que estamos desenvolvendo um sistema de pedidos de café, onde você pode adicionar vários complementos (como leite, açúcar, chantilly) ao seu café. Usaremos o padrão Decorator para adicionar dinamicamente esses complementos ao café.
Definindo a Interface Componente
public interface Cafe {
double getCusto();
String getIngredientes();
}Implementação do Componente Concreto
public class CafeSimples implements Cafe {
@Override
public double getCusto() {
return 1.0;
}
@Override
public String getIngredientes() {
return "Café";
}
}Implementação do Decorator
public abstract class CafeDecorator implements Cafe {
protected Cafe cafeDecorado;
public CafeDecorator(Cafe cafe) {
this.cafeDecorado = cafe;
}
@Override
public double getCusto() {
return cafeDecorado.getCusto();
}
@Override
public String getIngredientes() {
return cafeDecorado.getIngredientes();
}
}Decoradores Concretos
public class ComLeite extends CafeDecorator {
public ComLeite(Cafe cafe) {
super(cafe);
}
@Override
public double getCusto() {
return super.getCusto() + 0.5;
}
@Override
public String getIngredientes() {
return super.getIngredientes() + ", Leite";
}
}
public class ComAçucar extends CafeDecorator {
public ComAçucar(Cafe cafe) {
super(cafe);
}
@Override
public double getCusto() {
return super.getCusto() + 0.2;
}
@Override
public String getIngredientes() {
return super.getIngredientes() + ", Açúcar";
}
}Utilizando o Padrão Decorator
public class Main {
public static void main(String[] args) {
Cafe meuCafe = new CafeSimples();
System.out.println("Custo: " + meuCafe.getCusto() + "; Ingredientes: " + meuCafe.getIngredientes());
meuCafe = new ComLeite(meuCafe);
System.out.println("Custo: " + meuCafe.getCusto() + "; Ingredientes: " + meuCafe.getIngredientes());
meuCafe = new ComAçucar(meuCafe);
System.out.println("Custo: " + meuCafe.getCusto() + "; Ingredientes: " + meuCafe.getIngredientes());
}
}Neste exemplo, começamos com um CafeSimples e adicionamos dinamicamente complementos usando os decoradores ComLeite e ComAçucar. Cada decorador adiciona seu próprio custo e ingredientes ao café. Isso demonstra como o padrão Decorator permite a adição flexível de funcionalidades a objetos sem alterar os objetos existentes.