Padrão de design do decorador

Dec 27 2022
Posso me colocar em primeiro lugar, mesmo depois, se eu quiser. Temos uma interface chamada Payment contém um método de pagamento. E uma classe concreta DefaultPayment para implementar o método, o método pega o preço como argumento para fazer a lógica de pagamento.

Eu posso me colocar em primeiro lugar, mesmo depois se eu quiser

Você pode adicionar açúcar antes, ou até mesmo adicionar chocolate por cima depois de fazer sua xícara de Nescafé

Temos uma interface chamada Pagamento contém um método de pagamento .

E uma classe concreta DefaultPayment para implementar o método, o método pega o preço como argumento para fazer a lógica de pagamento.

public interface Payment {
    void pay(BigDecimal price);
}
public class DefaultPayment implements Payment {
    @Override
    public void pay(BigDecimal price) {
        System.out.println("Payed: " + price);
    }
}

public class Client {
    public static void main(String[] args) {
        Payment defaultPayment = new DefaultPayment();
        defaultPayment.pay(BigDecimal.valueOf(100));
    }
}

Digamos que haja um imposto sobre um produto que desejo pagar, o que posso fazer?

Podemos aproveitar nosso design, projetamos para uma interface, não para uma implementação.

Assim, podemos criar uma classe para Payment e deixá-la ser um tipo de Payment e ao mesmo tempo manter o Payment como propriedade, podendo receber o pagamento usando seu construtor, e chamaremos essa classe de TaxPaymentDecorator .

public class TaxPaymentDecorator implements Payment {
    private Payment payment;
    private BigDecimal tax = BigDecimal.ZERO;

    public TaxPaymentDecorator(Payment payment) {
        this.payment = payment;
    }

    @Override
    public void pay(BigDecimal price) {
        price = price.add(this.tax);
        payment.pay(price);
    }

    public BigDecimal getTax() {
        return tax;
    }

    public void setTax(BigDecimal tax) {
        this.tax = tax;
    }
}

E o cliente poderia usar o TaxPaymentDecorator da seguinte forma:

public class Client {
    public static void main(String[] args) {
        TaxPaymentDecorator taxPaymentDecorator = new TaxPaymentDecorator(new DefaultPayment());
        taxPaymentDecorator.setTax(BigDecimal.valueOf(50));
        taxPaymentDecorator.pay(BigDecimal.valueOf(100));
    }
}

public class DiscountPaymentDecorator implements Payment {
    private Payment payment;
    private BigDecimal discount = BigDecimal.ZERO;

    public DiscountPaymentDecorator(Payment payment) {
        this.payment = payment;
    }

    @Override
    public void pay(BigDecimal price) {
        price = price.subtract(discount);
        payment.pay(price);
    }

    public BigDecimal getDiscount() {
        return discount;
    }

    public void setDiscount(BigDecimal discount) {
        this.discount = discount;
    }
}

public class Client {
    public static void main(String[] args) {
        TaxPaymentDecorator taxPaymentDecorator = new TaxPaymentDecorator(new DefaultPayment());
        taxPaymentDecorator.setTax(BigDecimal.valueOf(50));
        DiscountPaymentDecorator discountPaymentDecorator = new DiscountPaymentDecorator(taxPaymentDecorator);
        discountPaymentDecorator.setDiscount(BigDecimal.valueOf(10));
        discountPaymentDecorator.pay(BigDecimal.valueOf(100));
    }
}

Diagrama UML

Diagrama UML do padrão de design do decorador

Princípios de design

Vou discutir três princípios de design aqui:

  1. Design para uma interface não para implementação: Como você pode ver, usar a interface Payment nos ajudou a usar o poder do OOP adicionando comportamento dinamicamente no tempo de execução sem nenhuma modificação para a implementação antiga.
  2. Faça seu design aberto para extensão e fechado para modificação: Este ponto está relacionado ao anterior, nossa implementação antiga não nos obrigou a fazer nenhuma modificação, mas estendemos a implementação antiga para adicionar novos comportamentos, e quero dizer aqui usando a interface em vez disso de projeto para implementação.
  3. Prefira composição a herança: o decorador usa a composição aqui ao invés de tentar usar herança, em geral usar herança pode causar problemas por aumentar o acoplamento, e poderíamos discutir isso em um blog separado.

Anexe responsabilidades adicionais a um objeto dinamicamente. Os decoradores fornecem uma alternativa flexível à subclassificação para estender a funcionalidade.

Em nosso exemplo anexamos duas responsabilidades dinamicamente em tempo de execução, adicionamos o imposto ao preço e subtraímos o desconto do preço, não fizemos subclassificação para o DefaultPayment, usamos o poder do OOP para adicionar nossos comportamentos de maneira flexível, e esse é o padrão Decorator .