This article is excerpted from This is How Design Patterns should Be Learned

1. Use the decorator mode to solve the problem of adding pancakes

Let’s take a look at the scene: most office workers have the habit of sleeping in late, and the time for going to work is very tight every morning. Therefore, many people find a more convenient way to solve the problem of breakfast in order to get more sleep. Some people may have pancakes for breakfast. You can add eggs or sausage to a pancake, but no matter how big it is, it’s still a pancake. For example, add some fruit to the cake, decorate the house, all in decorator mode.

The following code is used to simulate the business scenario of adding extra code to pancakes, starting with the case without the decorator pattern. First create a pancake Battercake class.


public class Battercake {

    protected String getMsg(a){
        return "Pancakes";
    }

    public int getPrice(a){
        return 5; }}Copy the code

Then create an egg pancake BattercakeWithEgg class.


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

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

Create a add eggs and sausage BattercakeWithEggAndSausage class.


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

    @Override
    // Add 1 sausage, add 2 yuan
    public int getPrice(a) {
        return super.getPrice() + 2; }}Copy the code

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

    }
		
Copy the code

The running result is shown in the figure below.

The results are fine. However, if a user wants a pancake with two eggs and one sausage, it can’t be created with the current class structure, nor can it be priced automatically, unless another class is created for customization. If the requirements change again, it is obviously not scientific to add customization all the time. The decorator pattern is used to solve the above problem. Start by creating an abstract Battercake class for pancakes.


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

Copy the code

Create a basic pancake (or basic meal) BaseBattercake.


public class BaseBattercake extends Battercake {
    protected String getMsg(a){
        return "Pancakes";
    }

    public int getPrice(a){ return 5; }}Copy the code

Then create an extension package to the abstract decorator BattercakeDecotator class.


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

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

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

Next, create the EggDecorator class.


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

    protected void doSomething(a) {}

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

    @Override
    protected int getPrice(a) {
        return super.getPrice() + 1; }}Copy the code

Create the SausageDecorator class.


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

    protected void doSomething(a) {}

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

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, please add an egg
        battercake = new EggDecorator(battercake);
        // Add 1 egg
        battercake = new EggDecorator(battercake);
        // Hungry, add 1 sausage
        battercake = new SausageDecorator(battercake);

        // The main difference with static proxies is the different responsibilities
        // Static proxies need not satisfy the IS-A relationship
        // Static proxies do enhancements that make the same responsibility different

        // Decorators are more about extension
        System.out.println(battercake.getMsg() + Total price:+ battercake.getPrice()); }}Copy the code

The running result is shown in the figure below.

Finally, look at the class diagram, as shown below.

Use decorator mode to extend log format output

To impress, let’s look at another application scenario. The requirements are roughly as follows: The system uses SLS service to monitor project logs and parse them in JSON format. Therefore, the logs in the project need to be encapsulated in JSON format and then printed. The existing logging system is constructed by Log4j + Slf4j framework. The client call is as follows.


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

This prints out random lines of strings. When considering converting it to JSON format, I use the decorator pattern. At present, there is a unified interface Logger and its concrete implementation class, the author wants to add is a decoration class and a decoration product class really packaged into JSON format. Create the DecoratorLogger class.


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 the other default implementations
}

Copy the code

Create the concrete component JsonLogger class.


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(a) {
        // Assembles some runtime information
        return newJSONObject(); }}Copy the code

As you can see, in JsonLogger, the various interfaces to Logger are wrapped in a JsonObject. The native logger.error(string) interface is still called when printing, but the string argument has been embellished. If there is an additional requirement, you can write another function to implement it. For example, error(Exception e), passing in only one Exception object, makes it very convenient to call. In addition, to keep the code and usage from changing too much during the transition, I added an internal factory class JsonLoggerFactory to JsonLogger (this class might be better moved to the DecoratorLogger). It contains a static method that provides the corresponding JsonLogger instance. Finally, in the new logging system, the usage is as follows.


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

    public static void main(String[] args) {

        logger.error("Error message");
    }
		
Copy the code

For the client side, the only difference is to change the LoggerFactory to JsonLoggerFactory. This implementation will be accepted and used by other developers more quickly and conveniently. Finally, look at the class diagram shown below.

The most essential feature of the decorator pattern is to remove the additional functionality of the original class and simplify the logic of the original class. From these two cases, we can conclude that abstract decorators are optional and can be chosen according to the business model.

Pay attention to “Tom play architecture” reply to “design pattern” can obtain the complete source code.

Tom play architecture: 30 real cases of design patterns (attached source code), the challenge of annual salary 60W is not a dream

This article is “Tom play structure” original, reproduced please indicate the source. Technology is to share, I share my happiness! If this article is helpful to you, welcome to follow and like; If you have any suggestions can also leave a comment or private letter, your support is my motivation to adhere to the creation. Pay attention to “Tom bomb architecture” for more technical dry goods!