Se você já trabalhou com orientação a objetos por algum tempo, provavelmente já se deparou com este dilema: como adicionar novas funcionalidades a um objeto sem criar uma explosão de subclasses? Imagine um sistema de componentes gráficos onde você precisa combinar bordas, sombras, cores e efeitos de diferentes formas. Criar uma subclasse para cada combinação possível rapidamente se torna insustentável.
É exatamente para resolver esse tipo de problema que existe o Decorator, um dos padrões de projeto estruturais mais elegantes do catálogo GoF (Gang of Four).
Neste artigo, vou explicar o que é o padrão Decorator, quando ele deve ser usado e como implementá-lo na prática com um exemplo completo em Java.
O que é o padrão Decorator?
O Decorator é um padrão de design estrutural que permite adicionar comportamento a objetos individuais de forma dinâmica e transparente, sem afetar outros objetos da mesma classe.
Em vez de estender uma classe para adicionar uma nova responsabilidade (o que gera herança rígida e múltiplas subclasses), o Decorator envolve o objeto original com uma camada adicional de comportamento — como se fosse uma "embalagem" que soma funcionalidades sem alterar a estrutura interna do objeto decorado.
O resultado é um sistema muito mais flexível e modular, no qual novas combinações de comportamento podem ser criadas em tempo de execução, simplesmente combinando decoradores diferentes.
Vantagens do Decorator
- Flexibilidade e modularidade: permite estender o comportamento de objetos individuais sem afetar os demais.
- Evita a explosão de subclasses: ao invés de criar uma classe para cada combinação de funcionalidades, você combina decoradores.
- Responsabilidade única: cada decorador cuida de uma única responsabilidade, mantendo o código mais limpo e coeso.
Quando usar o Decorator?
Esse padrão é especialmente útil quando:
- Você precisa adicionar responsabilidades a objetos dinamicamente, sem modificar seu código-fonte.
- Você quer evitar a criação de subclasses excessivas para representar cada combinação possível de comportamentos.
- Você busca uma alternativa à herança para estender funcionalidades de forma mais flexível.
Estrutura do Padrão
O Decorator é composto por quatro elementos principais:
1. Componente (Component)
Define a interface comum para os objetos que podem receber responsabilidades adicionais. É o contrato que tanto os componentes concretos quanto os decoradores devem seguir.
// Componente
interface Component {
void draw();
}
2. Componente Concreto (Concrete Component)
Implementa o comportamento básico do componente — é o objeto "puro", sem nenhuma decoração aplicada ainda.
// Componente Concreto
class Button implements Component {
public void draw() {
System.out.println("Drawing a button");
}
}
3. Decorador (Decorator)
Essa é a peça central do padrão. O decorador:
- Mantém uma referência para um objeto do tipo
Component. - Implementa a mesma interface do componente base, garantindo que possa ser usado no lugar dele.
- Encapsula o componente original e adiciona um comportamento extra.
- Pode ser estendido por subclasses para implementar responsabilidades específicas.
// Decorador
abstract class BorderDecorator implements Component {
protected Component component;
public BorderDecorator(Component component) {
this.component = component;
}
public void draw() {
component.draw();
drawBorder();
}
protected abstract void drawBorder();
}
Note que o método draw() primeiro chama o comportamento original (component.draw()) e depois adiciona o novo comportamento (drawBorder()). Essa é a essência do padrão: compor comportamentos em camadas.
4. Decorador Concreto (Concrete Decorator)
Estende o decorador abstrato e implementa a funcionalidade específica que será adicionada.
// Decorador Concreto
class RedBorderDecorator extends BorderDecorator {
public RedBorderDecorator(Component component) {
super(component);
}
protected void drawBorder() {
System.out.println("Drawing a red border");
}
}
Colocando tudo em prática
Com as classes definidas, podemos ver o padrão em ação:
public class Main {
public static void main(String[] args) {
// Componente original
Component button = new Button();
// Decorando o componente original com uma borda vermelha
Component decoratedButton = new RedBorderDecorator(button);
// Desenhando o componente decorado
decoratedButton.draw();
}
}
Saída esperada:
Drawing a button
Drawing a red border
Perceba que o botão original não foi alterado. Ele continua existindo como uma implementação independente. A borda vermelha foi adicionada dinamicamente, "envolvendo" o botão em uma nova camada de comportamento. Se amanhã precisarmos de um botão com borda azul, ou com borda e sombra combinadas, basta criar (ou combinar) novos decoradores — sem tocar na classe Button.
Por que isso importa?
O grande ganho do Decorator está em respeitar o princípio Aberto/Fechado (Open/Closed Principle): as classes ficam abertas para extensão, mas fechadas para modificação. Isso significa que podemos adicionar novos comportamentos ao sistema sem correr o risco de quebrar código já existente e testado.
Além disso, como os decoradores podem ser empilhados (um decorador pode envolver outro decorador), é possível construir combinações complexas de comportamento a partir de peças simples e reutilizáveis — algo muito mais elegante do que uma árvore de herança cheia de subclasses.
Conclusão
O padrão Decorator é uma ferramenta poderosa para quem busca flexibilidade no design de sistemas orientados a objetos. Ao substituir a herança rígida por composição dinâmica, ele permite adicionar responsabilidades a objetos de forma modular, reutilizável e sem efeitos colaterais sobre outras instâncias da mesma classe.
Esse padrão é amplamente utilizado em bibliotecas e frameworks conhecidos — por exemplo, o próprio java.io do Java (como BufferedReader envolvendo um FileReader) é um exemplo clássico de Decorator aplicado na prática.
Entender e saber aplicar o Decorator é um passo importante para escrever código mais limpo, extensível e alinhado com boas práticas de design orientado a objetos.
Top comments (0)