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

author author     2022-12-24     739

关键词:

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

作者 | 北极星小组

想要写好代码,设计模式(Design Pattern)是必不可少的基本功,设计模式是对面向对象设计(Object Oriented Design)中反复出现的一类问题的一种解决方案,本篇介绍装饰器模式(Decorator Pattern)。

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

  • 修改已有的类:违背开闭原则。
  • 增加新的子类:每次都得新增大量对应的类,随着功能的增加,子类越来越膨胀。

在此场景下,装饰器模式就可以体现出它的优势了,它允许在不修改原有对象的前提下,灵活的扩展已有类的功能。下面是装饰器模式的一个通用的类图:

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


△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 ----------

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

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

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

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

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

ios教你玩转uitableviewcontroller和tableview

iphone和Ipad开发中UITableViewController和TableView应该是用得比較多得控件。可是你是会由于写这些控件写得多了而厌烦。全部怎么让这个控件一直能用。怎么让这个控件写起来简单。是非常必须。特别是UITableViewController把dataSouce的业... 查看详情

两句命令教你玩转《黑客帝国》中的“代码雨”

1在系统中安装cmatrix$sudo apt-getupdate;sudoapt-getinstallcmatrix2执行命令$cmatrix 查看详情

实践篇教你玩转微服务--基于ddd的微服务架构落地实践之路

...服务架构的前身就是SOA,面向服务的编程。SOA是一种架构设计模式,也是一种思想。所以微服务理应继承了SOA的这种架构思想,所以我认为微服务就是那个白马,他将一个复杂系统,以业务的视角,分成独立的模块,每个模块都... 查看详情

教你玩转django(代码片段)

www.djangoproject.com官网https://docs.djangoproject.com/en/2.1/可以进来这里选择中文文档zh-hanshttps://docs.djangoproject.com/zh-hans/2.1/也就是这个。可以直接打开这个http://sqlitebrowser.org/数据库工具常用命令:runserver:?????????启动程序startproject???????... 查看详情

一张图教你玩转阿里云双11上云狂欢节

一年一度的双11狂欢节已经开启啦!下面一张图教你如何玩转阿里云双11上云狂欢节!双11主会场地址:http://click.aliyun.com/m/1000305076/  查看详情

设计模式---(简单工厂模式,工厂模式,抽象工程模式),单例模式,代理模式,装饰器(代码片段)

简单工厂模式 简单工厂模式并不属于GoF的23种设计模式。  那么为什么我要用工厂模式呢?请看下面的一段程序。#include <iostream>using namespace std;class Fruit public:    Fruit(str 查看详情

新生代工程师手把手教你玩转alluxio+ml(上篇)

...试、分析以及调优,我们邀请到Alluxio团队的核心开发工程师邱璐和孙守拙介绍Alluxio在机器学习场景下的最佳实践。针对此场景下常见的海量数据、海量(小)文件、高度并发等数据挑战。在【MachineLearning+Alluxio... 查看详情

手把手教你玩转git

文章已托管到GitHub,大家可以去GitHub查看下载!并搜索关注微信公众号码出Offer领取各种学习资料!在这里插入图片描述Git应用一、初识Git1.1Git的简史同生活中的许多伟大事物一样,Git诞生于一个极富纷争大举创新的年代。Linus... 查看详情

新生代工程师手把手教你玩转alluxio+ml(下篇)

Part1AlluxioPOSIXAPI自测性能使用AlluxioPOSIX读文件速度能到多少?和从云上读取数据相比,使用AlluxioPOSIX接口:更快吗?快多少?上篇中提到了很多Alluxio为了加速读取数据做的各种各样的优化,那么对于用户来... 查看详情

5分钟教你玩转sklearn机器学习(上)

假期结束,你的状态有没有回归?那么,放空脑袋后,先来学习学习,欢迎大家继续关注腾讯云技术社区。作者:赵成龙 这是一篇很难写的文章,因为我希望这篇文章能对大家有所帮助。我不会给大家介绍机器学习,数据挖... 查看详情

20个精美图表,教你玩转pyecharts可视化

作者|俊欣来源|关于数据分析与可视化本篇文章我们将继续聚焦c模块并且用它来绘制精美的图表,希望读者在看完之后会有不少收获01内嵌饼状图内接一个环状的饼图,里面还有一个饼状的图(Pie().add(series_name="访问... 查看详情

教你玩转iphone超实用的3dtouch功能!

在平时使用iPhone过程中是否发现很多功能都不知道怎么用,还吐槽苹果系统不够人性化,现在先给大家推荐一波三维触控的使用技巧!三维触控(3DTouch)仅iPhone6s或以上机型支持三维触控(3DTouch),开启功能:设置→通用→辅... 查看详情

教你玩转友盟应用性能监控u-apm平台

目录前言正文一、U-APM 应用性能监控平台介绍        1. 大核心优势        2.U-APM 与其他产品功能对比二、集成友盟 SDK 步骤        第一步、进入U-APM功能首页        第二步、填写应用程序信息并注册    ... 查看详情

设计模式——十七:装饰器模式(代码片段)

@目录什么是装饰器模式?为什么要用装饰器模式使用装饰器模式前引入装饰器模式装饰器模式优缺点装饰器模式优点装饰器模式缺点装饰器模式应用场景扩展-Java中的装饰器模式什么是装饰器模式?装饰器模式的定义:Attachadditi... 查看详情

手把手教你玩转12306验证码的秘密!

12306相信对很多小伙伴都不陌生,假如问你对这个网站的印象的时候,你不是会立即想起那个坑爹的验证码,而正是这个验证码,也一时间成为小伙伴们讨论的话题,今天思梦PHP就给大家带来他的实现办法,纯属技术交流,有什... 查看详情

30分钟带你玩转正则表达式

...bsp;          30分钟带你玩转正则表达式 定义:正则表达式说白了就是有普通字符、以及特殊字符组成的文子模式。{匹配模式标准}正则表达式将会作为一个模板与所搜索的字符串进行匹配... 查看详情

手把手教你玩转git分布式版本控制系统!

目录 Git诞生历史 Git环境准备Git安装部署Git常用命令Git基本操作Git管理分支结构Git管理标签GitLab安装部署GitHub托管服务Git客户端工具 1Git诞生历史 我想大家还记得Linustorvalds在1991年时发布了Linux操作系统吧,从那以... 查看详情

unity3d超多体位教你玩转unityevent

参考技术ATips:更多资料:UnityAction和UnityEvent的用法详解-安宁技术博客-CSDN博客http://blog.csdn.net/inlet511/article/details/46822907Unity泛型T的用法-小唐的专栏-CSDN博客http://blog.csdn.net/tang_shiyong/article/details/46778497在Inspector中设置添加回调函... 查看详情