Padrões de Projetos - Decorator

De Aulas

Afluentes: Modelos, métodos e técnicas da engenharia de software

Descrição

O padrão Decorator é usado para adicionar comportamentos ou responsabilidades adicionais a um objeto dinamicamente, sem alterar sua estrutura básica. Ele é especialmente útil quando queremos adicionar funcionalidades de maneira flexível, sem precisar criar subclasses para cada combinação de funcionalidades possíveis.

Como Funciona o Decorator

  1. Componente: Interface ou classe abstrata que define a interface comum para os objetos que podem ter funcionalidades adicionadas.
  2. Componente Concreto: Implementação básica do componente que pode receber as funcionalidades adicionais.
  3. Decorator: Classe abstrata que implementa a interface Componente e armazena uma referência a um componente. Ela delega as chamadas para o componente, mas pode adicionar comportamento adicional.
  4. Decorator Concreto: Implementa o comportamento adicional específico ao redor do componente.

Exemplo em Java

Vamos implementar um exemplo simples com uma interface Café, onde temos um café básico e decoradores que adicionam funcionalidades como leite e açúcar.

Interface Cafe (Componente)

Define a interface comum para o objeto que será decorado, com os métodos descricao() e preco().

// Interface Componente
interface Cafe {
    String descricao();
    double preco();
    void mostrar();
}

Classe CafeSimples (Componente Concreto)

Implementa Cafe e representa um café simples sem adições.

// Componente Concreto - Café simples
class CafeSimples implements Cafe {
    @Override
    public String descricao() {
        return "Café simples";
    }

    @Override
    public double preco() {
        return 2.0;
    }

    @Override
    public void mostrar() {
        System.out.println(descricao() + " custa R$" + preco());
    }
}

Classe CafeDecorator (Decorator)

É uma classe abstrata que implementa Cafe e mantém uma referência a um objeto Cafe. Ela delega as chamadas de descricao() e preco() para o objeto referenciado.

// Decorator abstrato - Implementa a interface Cafe e contém uma referência a um Cafe
abstract class CafeDecorator implements Cafe {
    protected Cafe cafeDecorado;

    public CafeDecorator(Cafe cafeDecorado) {
        this.cafeDecorado = cafeDecorado;
    }

    @Override
    public String descricao() {
        return cafeDecorado.descricao();
    }

    @Override
    public double preco() {
        return cafeDecorado.preco();
    }

    @Override
    public void mostrar() {
        System.out.println(descricao() + " custa R$" + preco());
    }
}

Classe LeiteDecorator (Decorator Concreto)

Decora o café com leite, adicionando comportamento e alterando a descrição e o preço.

// Decorator Concreto - Adiciona leite ao café
class LeiteDecorator extends CafeDecorator {
    public LeiteDecorator(Cafe cafeDecorado) {
        super(cafeDecorado);
    }

    @Override
    public String descricao() {
        return cafeDecorado.descricao() + ", com leite";
    }

    @Override
    public double preco() {
        return cafeDecorado.preco() + 0.5;
    }
}

Classe AcucarDecorator (Decorator Concreto)

Decora o café com açúcar, adicionando comportamento e alterando a descrição e o preço.

// Decorator Concreto - Adiciona açúcar ao café
class AcucarDecorator extends CafeDecorator {
    public AcucarDecorator(Cafe cafeDecorado) {
        super(cafeDecorado);
    }

    @Override
    public String descricao() {
        return cafeDecorado.descricao() + ", com açúcar";
    }

    @Override
    public double preco() {
        return cafeDecorado.preco() + 0.2;
    }
}

Classe Principal

// Classe principal para testar os Decorators
public class Main {
    public static void main(String[] args) {
        // Café simples
        Cafe cafe = new CafeSimples();
        cafe.mostrar();

        // Café com leite
        Cafe cafeComLeite = new LeiteDecorator(cafe);
        cafeComLeite.mostrar();

        // Café com leite e açúcar
        Cafe cafeComLeiteEAcucar = new AcucarDecorator(cafeComLeite);
        cafeComLeiteEAcucar.mostrar();
    }
}

Saída Esperada

Café simples custa R$2.0
Café simples, com leite custa R$2.5
Café simples, com leite, com açúcar custa R$2.7

Vantagens do Decorator

  • Flexibilidade: Adiciona funcionalidades aos objetos de forma dinâmica e flexível, sem criar subclasses para cada combinação de funcionalidades.
  • Extensibilidade: Novos decorators podem ser criados e combinados sem alterar o código existente.

Desvantagens do Decorator

  • Complexidade: Muitos decorators encadeados podem tornar o código mais difícil de entender e depurar.
  • Muitos Objetos: Cada decorator cria um novo objeto, o que pode aumentar a sobrecarga de memória em casos complexos.

Resumo

O padrão Decorator é ideal para situações onde você precisa de funcionalidades adicionais que podem variar dinamicamente, como adicionar ingredientes em uma bebida ou características em uma interface de usuário.