百度工程师教你玩转设计模式(装饰器模式)
作者 | 北极星小组
想要写好代码,设计模式(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 ----------
推荐阅读【技术加油站】系列:

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
"陆战之王"大润发如何在零售业数字化转型中抢占先机?
作者:大润发大数据团队 自 1998 年在上海开设第一家大型超市以来,大润发已在中国大陆地区成功开设近 500 家综合性大型超市,覆盖全国 29 个省市及自治区达 230 多个城市,年销售额过千亿。大润发优鲜、淘鲜达、饿了么、天猫超市线上平台用户达到 6900 万。活跃用户超过 1650 万,一线城市平均每店日均线上订单量突破 2000单。 作为国内大型连锁量贩超市的先导者,大润发在物流仓储、商品管理等领域很早就引入了大数据技术进行智能决策分析,帮助优化管理精度、提高管理水平。大数据平台也跟随技术浪潮多次迭代升级,沉淀出一整套特有的数据子系统和大数据处理方案。 大润发大数据业务起步较早、数据累积和历史组件较多,同时为了保证技术的前沿性,因此逐步发展出了多分析引擎的架构。一方面可以根据各类型引擎的特点处理合适的在线数据产品,一方面也可以根据各类引擎的技术优势及使用效果调整使用场景调节使用权重。 然而,随着业务需求的多样化、复杂化以及数据系统的改造升级,原本一些下放门店库的查询需求统一收归中心化的数据中台处理,导致高 并发 且复杂的查询场景出现频次激增。面对此种场景,无论是能力还是效率,...
- 下一篇
使用 Github Copilot 等 AI 工具的程序员代码安全性更低
斯坦福大学的计算机科学家 Neil Perry、Megha Srivastava、Deepak Kumar 和 Dan Boneh在最新发布的一份研究中指出,接受 Github Copilot 等 AI 工具帮助的程序员编写的代码安全性要低于那些独立编码的程序员;且 AI 助手往往会在输出质量上误导开发者,造成“盲目自信”。 我们进行了第一次大规模用户研究,研究用户如何与 AI 代码助手交互,以解决跨不同编程语言的各种安全相关任务。总的来说,我们发现能够接触到基于 OpenAI 的 codex-davinci-002 模型的 AI 助手的参与者编写的代码安全性明显低于没有接触到的参与者。此外,与没有机会接触到 AI 助手的参与者相比,有机会接触到 AI 助手的参与者更有可能认为他们编写了安全代码。我们发现那些不太信任 AI 而更多地使用提示的语言和格式(例如re-phrasing, adjusting temperature)的参与者提供的代码安全漏洞较少。最后,为了更好地指导未来基于 AI 的代码助手的设计,我们对参与者的语言和互动行为进行了深入分析,并发布了我们的 user int...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- Windows10,CentOS7,CentOS8安装Nodejs环境
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS8编译安装MySQL8.0.19
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Docker使用Oracle官方镜像安装(12C,18C,19C)