Interesting talk about the decorator mode, so that you will never forget it for a lifetime

This article is excerpted from "This is how design patterns should be learned"

1 Use the decorator pattern to solve the problem of over-coding of pancakes

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

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


public class Battercake {

    protected String getMsg(){
        return "煎饼";
    }

    public int getPrice(){
        return 5;
    }

}

复制代码

Then create a BattercakeWithEgg class that adds eggs.


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

    @Override
    //加1个鸡蛋加1元钱
    public int getPrice() {
        return super.getPrice() + 1;
    }
}

复制代码

Create another BattercakeWithEggAndSausage class that adds both eggs and sausage.


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

    @Override
    //加1根香肠加2元钱
    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() + ",总价格:" + battercake.getPrice());

        Battercake battercakeWithEgg = new BattercakeWithEgg();
        System.out.println(battercakeWithEgg.getMsg() + ",总价格:" + 
			battercakeWithEgg.getPrice());

        Battercake battercakeWithEggAndSausage = new BattercakeWithEggAndSausage();
        System.out.println(battercakeWithEggAndSausage.getMsg() + ",总价格:" + 
			battercakeWithEggAndSausage.getPrice());

    }
		
复制代码

The running result is shown in the figure below.

file

The result of operation is no problem. However, if the user needs a pancake with 2 eggs and 1 sausage, it cannot be created with the current class structure, nor can the price be automatically calculated unless another class is created for customization. If the demand changes again, it is obviously unscientific to keep adding customization. The decorator pattern is used below 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 base meal) BaseBattercake.


public class BaseBattercake extends Battercake {
    protected String getMsg(){
        return "煎饼";
    }

    public int getPrice(){ return 5;  }
}

复制代码

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


public abstract class BattercakeDecorator extends Battercake {
    //静态代理,委派
    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();
    }
}

复制代码

Then 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个鸡蛋";
    }

    @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根香肠";
    }
    @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;
        //买一个煎饼
        battercake = new BaseBattercake();
        //煎饼有点小,想再加1个鸡蛋
        battercake = new EggDecorator(battercake);
        //再加1个鸡蛋
        battercake = new EggDecorator(battercake);
        //很饿,再加1根香肠
        battercake = new SausageDecorator(battercake);

        //与静态代理的最大区别就是职责不同
        //静态代理不一定要满足is-a的关系
        //静态代理会做功能增强,同一个职责变得不一样

        //装饰器更多考虑的是扩展
        System.out.println(battercake.getMsg() + ",总价:" + battercake.getPrice());
    }
}

复制代码

The running result is shown in the figure below.

file

Finally, look at the class diagram, as shown in the following figure.

file

2 Extend log format output with decorator pattern

In order to deepen the impression, let's look at another application scenario. The requirements are roughly as follows. The system uses the SLS service to monitor the project log, which is parsed in JSON format. Therefore, it is necessary to encapsulate the log in the project into JSON format and then print it. The existing log system is built using the Log4j + Slf4j framework. The client call is as follows.


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

这样打印出来的是毫无规则的一行行字符串。当考虑将其转换成JSON格式时,笔者采用装饰器模式。目前有的是统一接口Logger和其具体实现类,笔者要加的就是一个装饰类和真正封装成JSON格式的装饰产品类。创建装饰器类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) {

    }
    //省略其他默认实现
}

复制代码

创建具体组件JsonLogger类。


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() {
        //拼装了一些运行时的信息
        return new JSONObject();
    }
}

复制代码

可以看到,在JsonLogger中,对于Logger的各种接口,我们都用JsonObject对象进行一层封装。在打印的时候,最终还是调用原生接口logger.error(string),只是这个String参数已经被装饰过了。如果有额外的需求,则可以再写一个函数去实现。比如error(Exception e),只传入一个异常对象,这样在调用时就非常方便。 另外,为了在新老交替的过程中尽量不改变太多代码和使用方式,笔者又在JsonLogger中加入了一个内部的工厂类JsonLoggerFactory(这个类转移到DecoratorLogger中可能更好一些)。它包含一个静态方法,用于提供对应的JsonLogger实例。最终在新的日志体系中,使用方式如下。


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

    public static void main(String[] args) {

        logger.error("错误信息");
    }
		
复制代码

对于客户端而言,唯一与原先不同的地方就是将LoggerFactory改为JsonLoggerFactory即可,这样的实现,也会更快更方便地被其他开发者接受和习惯。最后看如下图所示的类图。

file

装饰器模式最本质的特征是将原有类的附加功能抽离出来,简化原有类的逻辑。通过这样两个案例,我们可以总结出来,其实抽象的装饰器是可有可无的,具体可以根据业务模型来选择。

关注『 Tom弹架构 』回复“设计模式”可获取完整源码。

【推荐】Tom弹架构:30个设计模式真实案例(附源码),挑战年薪60W不是梦

本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。关注『 Tom弹架构 』可获取更多技术干货!

Guess you like

Origin juejin.im/post/7025517932885049357