Wzór projektowy dekoratora

Dec 27 2022
Mogę postawić siebie na pierwszym miejscu, nawet jeśli chcę. Mamy interfejs o nazwie Płatność zawiera metodę płatności. I konkretna klasa DefaultPayment do wdrożenia metody, metoda przyjmuje cenę jako argument do wykonania logiki płatności.

Mogę postawić siebie na pierwszym miejscu, nawet jeśli chcę

Możesz dodać cukier przed, a nawet czekoladę na wierzchu po zrobieniu filiżanki Nescafe

Mamy interfejs o nazwie Płatność zawiera metodę płatności .

I konkretna klasa DefaultPayment do wdrożenia metody, metoda przyjmuje cenę jako argument do wykonania logiki płatności.

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));
    }
}

Załóżmy, że jest podatek od produktu, za który chcę zapłacić, co mogę zrobić?

Możemy skorzystać z naszego projektu, który zaprojektowaliśmy dla interfejsu, a nie dla implementacji.

Możemy więc stworzyć klasę dla Payment i pozwolić jej być typem Payment , a jednocześnie zachować Payment jako właściwość, która mogłaby pobierać płatność za pomocą swojego konstruktora, a my nazwiemy tę klasę 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;
    }
}

A klient może użyć TaxPaymentDecorator w następujący sposób:

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));
    }
}

Diagram UML-a

Diagram wzorca projektowego dekoratora UML

Zasady projektowania

Omówię tutaj trzy zasady projektowania:

  1. Zaprojektuj interfejs, a nie implementację: jak widzisz, użycie interfejsu Płatności pomogło nam wykorzystać moc OOP poprzez dynamiczne dodawanie zachowania w czasie wykonywania bez żadnych modyfikacji starej implementacji.
  2. Otwórz swój projekt na rozszerzenie, zamknij na modyfikację: Ten punkt jest powiązany z poprzednim, nasza stara implementacja nie zmuszała nas do żadnych modyfikacji, ale rozszerzyliśmy starą implementację, aby dodać nowe zachowania, i mam na myśli tutaj użycie interfejsu projektu do realizacji.
  3. Wolisz kompozycję niż dziedziczenie: dekorator używa tutaj kompozycji zamiast próbować używać dziedziczenia, generalnie użycie dziedziczenia może powodować problemy przez zwiększenie sprzężenia i moglibyśmy omówić to w osobnym blogu.

Dynamicznie dołączaj dodatkowe obowiązki do obiektu. Dekoratory zapewniają elastyczną alternatywę dla podklas w celu rozszerzenia funkcjonalności.

W naszym przykładzie dynamicznie połączyliśmy dwa obowiązki w czasie wykonywania, dodaliśmy podatek do ceny i odjęliśmy rabat od ceny, nie zrobiliśmy podklasy dla DefaultPayment, wykorzystaliśmy moc OOP do dodania naszych zachowań w elastyczny sposób, i to jest Wzorzec Dekoratora .