当前最新:百度工程师教你玩转设计模式(装饰器模式)

2022-12-23 14:10:42 来源:51CTO博客

作者 | 北极星小组


(资料图)

想要写好代码,设计模式(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 ----------

推荐阅读【技术加油站】系列:

​​百度工程师教你玩转设计模式(工厂模式)​​

​​百度工程师教你玩转设计模式(适配器模式)​​

​​百度工程师教你玩转设计模式(单例模式)​​

标签: 任务处理 设计模式 处理完成

上一篇:全球新动态:[ Linux ] 死锁以及如何避免死锁
下一篇:SpringBoot2.x系列教程48--多数据源配置之AOP动态切换数据源