百度工程师教你玩转设计模式(装饰器模式)
作者 | 北极星小组

想要写好代码,规划形式(Design Pattern)是必不可少的基本功,规划形式是对面向目标规划(Object Oriented Design)中重复出现的一类问题的一种解决方案,本篇介绍装修器形式(Decorator Pattern)。

在咱们日常的开发过程中,一个最常见的场景就是在已有的基础上新增功用,常规的做法有以下几种:

  • 修正已有的类:违反开闭原则

  • 增加新的子类:每次都得新增很多对应的类,跟着功用的增加,子类越来越膨胀。

在此场景下,装修器形式就能够体现出它的优势了,它允许在不修正原有目标的前提下,灵敏的扩展已有类的功用。下面是装修器形式的一个通用的类图:

百度工程师教你玩转设计模式(装饰器模式)
△UML

其中的各个类的作用如下:

  • 笼统组件(Component): 可所以接口或者笼统类,它界说了详细类以及装修器所具有的方法。

  • 详细组件(ComponentA, ComponentB):详细的组件,完结或者承继自笼统组件。能够理解成上述场景中已存在的类。

  • 笼统装修器(Decorator): 一般为笼统类,持有一个被装修的目标,界说了详细装修器的方法。此类非有必要也能够没有,详细装修器也可直接承继或者完结笼统组件。

  • 详细装修器(DecoratorX, DecoratorY): 详细的装修器,承继自笼统装修器(也可直接承继自笼统组件),扩展了笼统组件的某些功用。

下面,将经过3个详细的事例的解说装修器的运用方式,便利大家进一步的理解。

一、装修器在使命处理场景的运用

在实际的开发中,咱们经常需求界说不同的类来处理各种不同的使命。假设一个这样的场景,咱们的体系有多个详细的类,用来处理不同类型的使命。现在需求增加一个功用,就是在处理完使命后宣布一条音讯。针对这个场景,运用装修器形式的完结思路如下:

  • 笼统组件(TaskProcessor):处理使命的笼统类(亦可经过接口完结),界说一个通用的使命处理方法process()。

  • 详细组件(TaskProcessorA, TaskProcessorB): 负责完结详细的使命处理逻辑

  • 笼统装修器(TaskProcessDecorator):持有一个使命处理目标实例

  • 详细装修器(AfterTaskProcessDecorator):完结详细的使命处理完结后的音讯通知扩展能力

详细的代码如下:

package com.baidu.demo;
public class Decorator {
    // 笼统组件
    static abstract class TaskProcessor {
        abstract void process();
    }
    // 详细组件
    static class TaskProcessorA extends TaskProcessor {
        @Override
        void process() {
            System.out.println("TaskProcessorA处理完结");
        }
    }
    // 详细组件
    static class TaskProcessorB extends TaskProcessor {
        @Override
        void process() {
            System.out.println("TaskProcessorB处理完结");
        }
    }
    // 笼统装修器
    static abstract class TaskProcessDecorator extends TaskProcessor {
        protected TaskProcessor processor;
        public TaskProcessDecorator(TaskProcessor processor) {
            this.processor = processor;
        }
        abstract void process();
    }
    // 详细装修器
    static class AfterTaskProcessDecorator extends TaskProcessDecorator {
        public AfterTaskProcessDecorator(TaskProcessor processor) {
            super(processor);
        }
        @Override
        void process() {
            processor.process();
            afterProcess();
        }
        void afterProcess() {
            System.out.println("使命处理完毕,发送音讯...");
        }
    }
    public static void main(String[] args) {
        // 扩展之前
        System.out.println("==========before==========");
        TaskProcessor processorA = new TaskProcessorA();
        processorA.process();
        TaskProcessor processorB = new TaskProcessorB();
        processorB.process();
        // 装修器扩展之后:TaskProcessorA TaskProcessorB并未做任何修正,即可完结功用的扩展
        System.out.println("==========after==========");
        TaskProcessor decoratorA = new AfterTaskProcessDecorator(processorA);
        decoratorA.process();
        TaskProcessor decoratorB = new AfterTaskProcessDecorator(processorB);
        decoratorB.process();
    }
}
// 输出成果如下
==========before==========
TaskProcessorA处理完结
TaskProcessorB处理完结
==========after==========
TaskProcessorA处理完结
使命处理完毕,发送音讯...
TaskProcessorB处理完结
使命处理完毕,发送音讯...

二、装修器在文件IO场景的运用

装修器形式,一个典型的运用就是文件IO操作,最基础的类完结字节省读取类,运用装修器形式能够封装文件字节省读取类,然后能够继续封装可缓存的文件字节省读取类,在项目中按需运用。详细完结如下:

  • InputStream:详细组件,完结读取字节省。

  • FileInputStream:详细装修器,作为InputStream的子类,扩展文件操作。

  • BufferedInputStream:详细装修器,作为FileInputStream的子类,扩展缓存操作。

详细代码如下:

//详细组件,完结读取字节省
public abstract class InputStream {
    public int read(byte b[], int off, int len) {}
}
//详细装修器,作为InputStream的子类,扩展文件操作
public class FileInputStream extends InputStream {
    protected InputStream in;
    public FileInputStream(String name) {
        InputStream in = ... //此处省掉,经过文件名创建目标
        this.in = in;
    }
    public int read(byte b[], int off, int len) {
        return this.in.read(b, off, len);
    }
}
//详细装修器,作为FileInputStream的子类,扩展缓存操作
public class BufferedInputStream extends FileInputStream {
    protected FileInputStream in;
    protected byte[] buffer;
    public BufferedInputStream(FileInputStream in) {
        this.in = in;
    }
    public int read(byte b[], int off, int len) {
        if (this.buffer == null || this.buffer.length == 0) {
            this.in.read(this.buffer, 0, in.lenght());
        }
        System.arraycopy(this.buffer, off, b, 0, len);
        ...
    }
}
public static void main(String[] args) {
    FileInputStream fs = new FileInputStream('./test.log');
    BufferedInputStream bs = new BufferedInputStream(fs);
    byte[] b;
    bs.read(b, 0, 1);
}

三、装修器在日志体系场景的运用

在日志体系中,一般常用日志的等级分别为 DEBUG(调试)、INFO(运转信息)、WARN(正告)、ERROR(过错),一旦发生过错等级的日志后,则需求触发报警通知相关人员及时进行跟进,报警方式一般有:邮件、短信、如流等,一般咱们会依据业务场景以组合的方式进行报警通知,运用装修器形式则能很好完结组合报警这一功用。

  • 笼统组件:Log接口笼统

  • 详细组件:Slf4j 详细日志类的完结

  • 笼统装修器:LogDecorator 日志装修器的基类

  • 详细装修器:MailLogDecorator、SMSLogDecorator、InfoFlowLogDecorator详细装修类

/**
 * 日志接口
 */
public interface Log {
    void debug(String message);
    void info(String message);
    void warn(String message);
    void error(String message);
}
/**
 * Slf4j 日志
 */
public class Slf4jLog implements Log {
    //日志记载目标
    private final Logger log = LoggerFactory.getLogger("system_log");
    @Override
    public void debug(String message) {
        if (log.isDebugEnabled()) {
             log.debug(message);
        }
    }
    @Override
    public void info(String message) {
        if (log.isInfoEnabled()) {
              log.info(message);
        }
    }
    @Override
    public void warn(String message) {
        if (log.isWarnEnabled()) {
            log.warn(message);
        }
    }
    @Override
    public void error(String message) {
        if (log.isErrorEnabled()) {
            log.error(message);
        }
    }
}
/**
 * 日志装修器
 */
public class LogDecorator implements Log {
    protected Log log;
    public LogDecorator(Log log) {
        this.log = log;
    }
    @Override
    public void debug(String message) {
        log.debug(message);
    }
    @Override
    public void info(String message) {
        log.info(message);
    }
    @Override
    public void warn(String message) {
        log.warn(message);
    }
    @Override
    public void error(String message) {
        log.error(message);
    }
}
/**
 * 邮件日志装修器
 */
public class MailLogDecorator extends LogDecorator {
    public MailLogDecorator(Log log) {
        super(log);
    }
    @Override
    public void warn(String message) {
        log.warn(message);
        mail(message);
    }
    @Override
    public void error(String message) {
        log.error(message);
        mail(message);
    }
    public void mail(String message) {
        //模仿邮件发送
        log.info("邮件已发送,信息:" + message);
    }
}
/**
 * 短信日志装修器
 */
public class SMSLogDecorator extends LogDecorator {
    public SMSLogDecorator(Log log) {
        super(log);
    }
    @Override
    public void error(String message) {
        log.error(message);
        send(message);
    }
    public void send(String message) {
        //模仿短信发送
        log.info("短信已发送,信息:" + message);
    }
}
/**
 * 如流日志装修器
 */
public class InfoflowLogDecorator extends LogDecorator {
    public InfoflowLogDecorator(Log log) {
        super(log);
    }
    @Override
    public void warn(String message) {
        log.warn(message);
        send(message);
    }
    @Override
    public void error(String message) {
        log.error(message);
        send(message);
    }
    public void send(String message) {
        //模仿如流发送
        log.info("如流音讯已发送,信息:" + message);
    }
}
/**
 * 日志测试类
 */
public class LogTest {
    /**
     * 测试日志装修器
     */
    @Test
    public void testLogDecorator() {
        Log log = new SMSLogDecorator(new InfoFlowLogDecorator(new MailLogDecorator(new Slf4jLog())));
        log.debug("体系调试敞开");
        log.info("体系正常运转");
        log.warn("数据为空正告");
        log.error("db 衔接过错");
    }
}
===========output=========
15:16:56.564 [main] DEBUG system_log - 体系调试敞开
15:16:56.566 [main] INFO  system_log - 体系正常运转
15:16:56.566 [main] WARN  system_log - 数据为空正告
15:16:56.566 [main] INFO  system_log - 邮件已发送,信息:数据为空正告
15:16:56.566 [main] INFO  system_log - 如流音讯已发送,信息:数据为空正告
15:16:56.566 [main] ERROR system_log - db 衔接过错
15:16:56.566 [main] INFO  system_log - 邮件已发送,信息:db 衔接过错
15:16:56.566 [main] INFO  system_log - 如流音讯已发送,信息:db 衔接过错
15:16:56.566 [main] INFO  system_log - 短信已发送,信息:db 衔接过错
Process finished with exit code 0

四、总结

如上几个事例,装修器的最大作用就是在不修正原有类的基础上扩展已有的功用,它符合开闭原则,而且完结也比较灵敏。

———- END ———-

推荐阅览【技能加油站】系列:

百度工程师教你玩转规划形式(工厂形式)

百度工程师教你玩转规划形式(适配器形式)

百度工程师教你玩转规划形式(单例形式)

百度工程师教你玩转设计模式(装饰器模式)