Design pattern - decorator design pattern

1. General

for instance

Fast food restaurants have fried noodles and fried rice. They can add eggs, ham and bacon as side dishes. Of course, adding side dishes requires extra money. The price of each side dish is usually different, so it will be more troublesome to calculate the total price.

The above figure is a class diagram using inheritance. Problems in using inheritance:

  • Poor scalability
    If we want to add another ingredient (ham sausage), we will find that we need to define a subclass for FriedRice and FriedNoodles respectively. If you want to add a fast food category (fried rice noodles), you need to define more subclasses.
  • Too many subclasses generated

Decorator pattern definition

Dynamically attach new functions to objects. In terms of object function extension, it is more flexible than inheritance. It refers to the mode of dynamically adding some responsibilities (i.e. adding additional functions) to the object without changing the existing object structure.

2. Structure

Roles in Decorator mode:

  • Abstract Component role: define an abstract interface to standardize objects ready to receive additional responsibilities.
  • Concrete Component role: implement abstract components and add some responsibilities to them by decorating roles.
  • Abstract Decorator role: inherits or implements abstract components and contains instances of specific components. The functions of specific components can be extended through their subclasses.
  • Concrete decorator role: implement relevant methods of abstract decoration and add additional responsibilities to specific component objects.

3. Cases

Use the decorator model to improve the fast food restaurant case

The class diagram is as follows

The code is as follows

//Fast food interface
public abstract class FastFood {
    private float price;
    private String desc;

    public FastFood() {
    }

    public FastFood(float price, String desc) {
        this.price = price;
        this.desc = desc;
    }

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

    public float getPrice() {
        return price;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public abstract float cost();  //Get price
}

//Fried rice
public class FriedRice extends FastFood {

    public FriedRice() {
        super(10, "Fried rice");
    }

    public float cost() {
        return getPrice();
    }
}

//Stir-Fried Noodles with Vegetables
public class FriedNoodles extends FastFood {

    public FriedNoodles() {
        super(12, "Stir-Fried Noodles with Vegetables");
    }

    public float cost() {
        return getPrice();
    }
}

//Ingredients
public abstract class Garnish extends FastFood {

    private FastFood fastFood;

    public FastFood getFastFood() {
        return fastFood;
    }

    public void setFastFood(FastFood fastFood) {
        this.fastFood = fastFood;
    }

    public Garnish(FastFood fastFood, float price, String desc) {
        super(price,desc);
        this.fastFood = fastFood;
    }
}

//Egg ingredients
public class Egg extends Garnish {

    public Egg(FastFood fastFood) {
        super(fastFood,1,"egg");
    }

    public float cost() {
        return getPrice() + getFastFood().getPrice();
    }

    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}

//Bacon ingredients
public class Bacon extends Garnish {

    public Bacon(FastFood fastFood) {

        super(fastFood,2,"Bacon");
    }

    @Override
    public float cost() {
        return getPrice() + getFastFood().getPrice();
    }

    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}

//Test class
public class Client {
    public static void main(String[] args) {
        //Order a fried rice
        FastFood food = new FriedRice();
        //Price spent
        System.out.println(food.getDesc() + " " + food.cost() + "element");

        System.out.println("========");
        //Order a fried rice with eggs
        FastFood food1 = new FriedRice();

        food1 = new Egg(food1);
        //Price spent
        System.out.println(food1.getDesc() + " " + food1.cost() + "element");

        System.out.println("========");
        //Order a fried noodles with bacon
        FastFood food2 = new FriedNoodles();
        food2 = new Bacon(food2);
        //Price spent
        System.out.println(food2.getDesc() + " " + food2.cost() + "element");
    }
}

benefit

  • Decorator mode can bring more flexible extension functions than inheritance, and it is more convenient to use. You can obtain diversified results with different behavior states by combining different decorator objects. Decorator mode has better expansibility than inheritance, and perfectly follows the opening and closing principle. Inheritance is a static additional responsibility, while decorator is a dynamic additional responsibility.
  • Decorative classes and decorated classes can develop independently and will not be coupled with each other. Decorative pattern is an alternative pattern of inheritance. Decorative pattern can dynamically expand the functions of an implementation class.

4. Usage scenario

  • When the system cannot be extended by inheritance or inheritance is not conducive to system expansion and maintenance.
    There are two main types of situations in which inheritance cannot be used:
    The first is that there are a large number of independent extensions in the system. In order to support each combination, a large number of subclasses will be generated, resulting in an explosive increase in the number of subclasses;
    The second type is because the class definition cannot inherit (such as final class)
  • Add responsibilities to a single object in a dynamic and transparent manner without affecting other objects.
  • When the functional requirements of an object can be added or revoked dynamically.

5.JDK source code analysis

The wrapper class in the IO stream uses decorator mode. BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter.
Let's take BufferedWriter as an example. First, let's see how to use BufferedWriter

public class Demo {
    public static void main(String[] args) throws Exception{
        //Create BufferedWriter object
        //Create FileWriter object
        FileWriter fw = new FileWriter("C:\\Users\\Think\\Desktop\\a.txt");
        BufferedWriter bw = new BufferedWriter(fw);

        //Write data
        bw.write("hello Buffered");

        bw.close();
    }
}

It really feels like a decorator mode. Next, let's look at their structure:

Summary

BufferedWriter uses decorator mode to enhance the Writer subclass and add buffer to improve the efficiency of writing data.

6. Difference between agent and decorator

Differences between static proxy and decorator modes:

  • Similarities:
    Must implement the same business interface as the target class
    Declare the target object in both classes
    Can enhance the target method without modifying the target class
  • difference:
    -Different purposes
    The decorator is to enhance the target object
    Static proxy is to protect and hide the target object
    Getting the target object is built differently
    The decorator is passed in from the outside, which can be passed through the construction method
    Static proxy is created inside the proxy class to hide the target object

Tags: Design Pattern

Posted on Wed, 03 Nov 2021 14:50:17 -0400 by Capnstank