Understand the decorator mode

Posted by jamesnkk on Sun, 13 Feb 2022 21:30:55 +0100

When learning the classic design pattern, the decorator pattern is really a headache, but it is not difficult to realize after mastering its basic idea.

First of all, the decorator has told us two basic information

  1. Something to be decorated can be seen as a core
  2. Things with decoration can be regarded as different properties or functions

It's very simple. Just two information is enough for us to write all the template code. What the decorator mode needs to do is to wrap different functions outside the core layer by layer.

First of all, if you look at more design patterns, you will know that "core class" and "decoration class" must be divided into abstract and concrete classes. In this example, coffee is our core, and the ingredients we need to add are our decoration.
But first, let's look at the final client call code:

public static void main(String[] args) {
        //Order a cup of coffee espresso - core
        Order order = new Espresso();
        //Add a little milk - garnish
        order = new Milk(order);
        //Add a little chocolate - decoration
        order = new Chocolate(order);

        System.out.println("altogether"+order.cost()+"Yuan:" + order.getDsc());
    }

Print results:

How, is it in line with our expectations? Next, let's see how to realize it!

It can be seen from the client code that this Order class is used from beginning to end. It receives not only the core implementation class Espresso, but also the decorator implementation classes Milk and Chocolate.
Then you can get conclusion 1. Both the abstract core class and the abstract decoration class are subclasses of this Order class!

Since the Order class is the top-level parent class, its attributes must be owned by its subclasses. Here we confirm these attributes as price and description / name (coffee and ingredients have separate prices and names)

Give the code of Order class:

public abstract class Order {
    public String dsc;//describe
    private long price = 0;//Price

    public String getDsc() {
        return dsc;
    }

    public void setDsc(String dsc) {
        this.dsc = dsc;
    }

    public long getPrice() {
        return price;
    }

    public void setPrice(long price) {
        this.price = price;
    }

    public abstract long cost();
}

It can be found that in addition to the getter and setter methods of the two properties, it leaves a cost method (to obtain the total price of the order) to the subclass implementation.

Next, you can write out the core class Espresso class. Of course, there is not only one kind of Coffee, so for scalability, don't be afraid to bother to determine a parent class for Espresso, namely Coffee coffee class

Coffee class code:

public class Coffee extends Order {
    //Get order price
    @Override
    public long cost() {
        return super.getPrice();
    }
}

It can be found that the Coffee class does nothing but returns the price of the Order class.

Espresso Code:

public class Espresso extends Coffee{
    public Espresso(){
        setDsc("Italian Coffee");
        setPrice(10);
    }
}

It can be seen that this class calls the methods of the parent class and assigns values to the two basic properties of the parent class. Let's take a look at the inheritance chain first.

Let's review the first line of client code

public static void main(String[] args) {
        //Espresso (Italian coffee)
        Order order = new Espresso();
        //Decorator milk
        order = new Milk(order);
        //Decorator chocolate
        order = new Chocolate(order);

        System.out.println("altogether"+order.cost()+"Yuan:" + order.getDsc());
    }

You can find that the dsc and price attributes of order have been assigned "Italian coffee" and "10" respectively.

Let's take a look at the second line of code. The construction parameter of the decoration class is the core class, which is easy to understand. To decorate an object, I have to hold it first.
It can be seen from conclusion 1 that the core class decoration class is all inherited from the Order class, so the construction parameter of the abstract decoration class is its parent class Order class, which is conclusion 2

So far, we have the core coffee, and the rest is to add ingredients. As above, we also write the decoration abstract class and decoration implementation class Milk and Chocolate respectively.

Decoration abstract class code:

public abstract class Decorator extends Order {

    private Order drink;

    public Decorator(Order drink){
        this.drink = drink;
    }

    @Override
    public long cost() {
        return super.getPrice() + drink.cost();
    }

    //Description of seasoning + description of coffee
    @Override
    public String getDsc(){
        return super.getDsc() + "/"  + drink.getDsc();
    }
}

The cost method is covered here, which turns the price of this order into the price of decorator's ingredients + the price of core coffee. It also covers the name of the order and adds the name of the decorator to the original description.

From the client code and conclusion 1, we can see the super Getxxx gets the properties of the decorator implementation class

Decorator implementation class code:

public class Milk extends Decorator {
    public Milk(Order obj) {
        super(obj);
        setDsc("milk");
        setPrice(2);
    }
}

public class Chocolate extends Decorator {
    public Chocolate(Order obj) {
        super(obj);
        setDsc("Chocolates");
        setPrice(1);
    }
}

The inheritance chain of decorator implementation class is like this

Is it very similar to the inheritance chain of the core class? Finally, we add a few more coffee subclasses and ingredient subclasses, and the whole diagram becomes very symmetrical!

Remember the most important conclusion

  1. Decorator and decoratee are from a parent class several times
  2. The decorator class aggregates the decoratee
  3. The decorator class needs to override the parent class method to realize the business logic based on the decorator's basic functions. In this way, the function of each layer of decorator will be improved accordingly

Looking at the client code again, do you feel that the decorator mode is simple and exquisite?!

public static void main(String[] args) {
        //Espresso (Italian coffee)
        Order order = new Espresso();
        //Decorator milk
        order = new Milk(order);
        //Chocolate decorator
        order = new Chocolate(order);

        System.out.println("altogether"+order.cost()+"Yuan:" + order.getDsc());
    }

The code under Java's IO package is the ultimate use of decorator mode
for instance:

  • InputStream is an abstract class Order
  • FileInputStream and others are core subclasses
  • FilterInputStream is Decorator, which contains decorators
  • DataInputStream is a subclass of Decorator

The concrete realization can be tasted slowly by yourself~

Topics: Java Design Pattern Back-end