Interesting talk about the decorator mode, so you won't forget it all your life

Posted by Clerma on Mon, 01 Nov 2021 12:44:53 +0100

This article is excerpted from "design patterns should be learned this way"

1. Use decorator mode to solve the problem of pancake overweight

Looking at such a scene, most office workers have the habit of sleeping in. They are very nervous at work every morning. Therefore, many people solve the breakfast problem in a more convenient way in order to get more sleep. Some people may eat pancakes for breakfast. You can add eggs or sausages to a pancake, but no matter how much you add, it's still a pancake. For another example, adding some fruit to the cake and decorating the house are all decorator modes.

Let's use code to simulate the business scenario of adding code to pancakes. First, let's look at the situation without decorator mode. First create a pancake Battercake class.

public class Battercake {

    protected String getMsg(){
        return "grilled savory crepe";
    }

    public int getPrice(){
        return 5;
    }

}

Then create a BattercakeWithEgg class for pancakes with eggs.

public class BattercakeWithEgg extends Battercake{
    @Override
    protected String getMsg() {
        return super.getMsg() + "+1 An egg";
    }

    @Override
    //Add an egg and 1 yuan
    public int getPrice() {
        return super.getPrice() + 1;
    }
}

Then create a BattercakeWithEggAndSausage class with both eggs and sausages.

public class BattercakeWithEggAndSausage extends BattercakeWithEgg{
    @Override
    protected String getMsg() {
        return super.getMsg() + "+1 Root sausage";
    }

    @Override
    //Add 1 sausage and 2 yuan
    public int getPrice() {
        return super.getPrice() + 2;
    }
}

Finally, write the client test code.

public static void main(String[] args) {

        Battercake battercake = new Battercake();
        System.out.println(battercake.getMsg() + ",Total price:" + battercake.getPrice());

        Battercake battercakeWithEgg = new BattercakeWithEgg();
        System.out.println(battercakeWithEgg.getMsg() + ",Total price:" + 
			battercakeWithEgg.getPrice());

        Battercake battercakeWithEggAndSausage = new BattercakeWithEggAndSausage();
        System.out.println(battercakeWithEggAndSausage.getMsg() + ",Total price:" + 
			battercakeWithEggAndSausage.getPrice());

    }
		

The operation results are shown in the figure below.

There is no problem with the operation results. However, if the user needs a pancake with 2 eggs and 1 sausage, the current class structure cannot be created, and the price cannot be calculated automatically, unless another class is created for customization. If the demand changes again, it is obviously unscientific to add customization all the time.

Let's use decorator mode to solve the above problem. First, create an abstract Battercake class for pancakes.

public abstract class Battercake {
    protected abstract String getMsg();
    protected abstract int getPrice();
}

Create a basic pancake (or basic package) BaseBattercake.

public class BaseBattercake extends Battercake {
    protected String getMsg(){
        return "grilled savory crepe";
    }

    public int getPrice(){ return 5;  }
}

Then create an abstract decorator battercakedecorator class that extends the package.

public abstract class BattercakeDecorator extends Battercake {
    //Static agent, delegate
    private Battercake battercake;

    public BattercakeDecorator(Battercake battercake) {
        this.battercake = battercake;
    }
    protected abstract void doSomething();

    @Override
    protected String getMsg() {
        return this.battercake.getMsg();
    }
    @Override
    protected int getPrice() {
        return this.battercake.getPrice();
    }
}

Next, create the egg decorator EggDecorator class.

public class EggDecorator extends BattercakeDecorator {
    public EggDecorator(Battercake battercake) {
        super(battercake);
    }

    protected void doSomething() {}

    @Override
    protected String getMsg() {
        return super.getMsg() + "+1 An egg";
    }

    @Override
    protected int getPrice() {
        return super.getPrice() + 1;
    }
}

Create the SausageDecorator class.

public class SausageDecorator extends BattercakeDecorator {
    public SausageDecorator(Battercake battercake) {
        super(battercake);
    }

    protected void doSomething() {}

    @Override
    protected String getMsg() {
        return super.getMsg() + "+1 Root sausage";
    }
    @Override
    protected int getPrice() {
        return super.getPrice() + 2;
    }
}

Then write the client test code.

public class BattercakeTest {
    public static void main(String[] args) {
        Battercake battercake;
        //Buy a pancake
        battercake = new BaseBattercake();
        //The pancake is a little small. I'd like to add another egg
        battercake = new EggDecorator(battercake);
        //Add another egg
        battercake = new EggDecorator(battercake);
        //I'm hungry. Add another sausage
        battercake = new SausageDecorator(battercake);

        //The biggest difference between static agent and static agent is different responsibilities
        //Static agents do not have to satisfy the is-a relationship
        //Static agents will be enhanced, and the same responsibility will become different

        //Decorators are more about expansion
        System.out.println(battercake.getMsg() + ",Total price:" + battercake.getPrice());
    }
}

The operation results are shown in the figure below.

Finally, let's look at the class diagram, as shown in the figure below.

2 expand log format output using decorator mode

To deepen our impression, let's take another look at an application scenario. The requirements are basically like this. The system uses SLS service to monitor the project log and parse it in JSON format. Therefore, the log in the project needs to be encapsulated in JSON format and then printed. The existing log system is built with Log4j + Slf4j framework. The client calls are as follows.

  private static final Logger logger = LoggerFactory.getLogger(Component.class);
        logger.error(string);
				

This prints out irregular lines of strings. When considering converting it to JSON format, the author adopts decorator mode. At present, there are unified interface Logger and its specific implementation classes. What the author wants to add is a decoration class and a decoration product class really encapsulated in JSON format. Create decorator class DecoratorLogger.

public class DecoratorLogger implements Logger {

    public Logger logger;

    public DecoratorLogger(Logger logger) {

        this.logger = logger;
    }

    public void error(String str) {}

    public void error(String s, Object o) {

    }
    //Omit other default implementations
}

Create the JsonLogger class for the specific component.

public class JsonLogger extends DecoratorLogger {
    public JsonLogger(Logger logger) {
        super(logger);
    }
        
    @Override
    public void info(String msg) {

        JSONObject result = composeBasicJsonResult();
        result.put("MESSAGE", msg);
        logger.info(result.toString());
    }
    
    @Override
    public void error(String msg) {
        
        JSONObject result = composeBasicJsonResult();
        result.put("MESSAGE", msg);
        logger.error(result.toString());
    }
    
    public void error(Exception e) {

        JSONObject result = composeBasicJsonResult();
        result.put("EXCEPTION", e.getClass().getName());
        String exceptionStackTrace = Arrays.toString(e.getStackTrace());
        result.put("STACKTRACE", exceptionStackTrace);
        logger.error(result.toString());
    }
    
    private JSONObject composeBasicJsonResult() {
        //Assembled some runtime information
        return new JSONObject();
    }
}

We can see that in JsonObject, we use JsonObject objects to encapsulate various interfaces of the Logger. When printing, the native interface logger.error(string) is finally called, but the String parameter has been decorated. If there are additional requirements, you can write another function to implement it. For example, error(Exception e), only one exception object is passed in, which is very convenient when calling.

In addition, in order not to change too much code and usage in the process of replacing the old with the new, the author adds an internal factory class JsonLoggerFactory to jsonloger (it may be better to transfer this class to DecoratorLogger). It contains a static method to provide the corresponding JsonLogger instance. Finally, in the new log system, it is used as follows.

    private static final Logger logger = JsonLoggerFactory.getLogger(Client.class);

    public static void main(String[] args) {

        logger.error("error message");
    }
		

For the client, the only difference from the original is to change the LoggerFactory to JsonLoggerFactory. This implementation will be accepted and used by other developers faster and more easily. Finally, look at the class diagram shown in the figure below.

The most essential feature of decorator pattern is to separate the additional functions of the original class and simplify the logic of the original class. Through these two cases, we can conclude that abstract decorators are dispensable and can be selected according to the business model.

This article is the original of "Tom bomb architecture". Please indicate the source for reprint. Technology lies in sharing, I share my happiness!