文章目录

  • 一、什么是装饰者模式
    • 1、装饰者模式原理
    • 2、装饰者模式四大角色
    • 3、代理、桥接、装饰器、适配器 4 种设计模式的区别
    • 4、装饰者模式的应用场景
    • 5、装饰者模式和代理模式的对比
    • 6、装饰者模式优缺点
    • 7、抽象装饰器(Decorator)是必需的吗
  • 二、实例1-煎饼
    • 使用装饰者模式优化代码
  • 三、实例2-日志
  • 四、JDK中IO流对装饰者模式的使用
  • 参考资料

一、什么是装饰者模式

装饰者模式(Decorator Pattern),也称为包装模式(Wrapper Pattern)、装饰器模式,是指在不改变原有对象的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能),属于结构型模式。

在 GoF 的《设计模式》一书中,对装饰者模式是这样理解的:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰者模式相比生成子类更灵活。

装饰者模式的核心是扩展功能。使用装饰者模式可以透明且动态地扩展类的功能。

装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。它主要的作用是给原始类添加增强功能。这也是判断是否该用装饰器模式的一个重要的依据。除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这个应用场景,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接口。

1、装饰者模式原理

装饰者模式主要用于透明且动态地扩展类的功能。

其实现原理为:让装饰器实现被包装类(ConcreteComponent)和相同的接口(Component)(使得装饰器与被扩展类类型一致),并在构造函数中传入该接口(Component)对象,然后就可以在接口需要实现的方法中在被包装类对象的现有个功能上添加新功能了。而且由于装饰器与被包装类属于同一类型(均为Component),且构造函数的参数为其实现接口类(Component),因此装饰器模式具备嵌套扩展功能,这样我们就能使用装饰器模式一层一层的对最底层被包装类进行功能扩展了。

2、装饰者模式四大角色

从UML类图中,我们可以看到,装饰者模式主要包含四种角色:

抽象组件(Component):可以是一个接口或者抽象类,其充当被装饰类的原始对象,规定了被装饰对象的行为;

具体组件(ConcreteComponent):实现/继承Component的一个具体对象,也即 被装饰对象;

抽象装饰器(Decorator):通用的装饰ConcreteComponent的装饰器,其内部必然有一个属性指向Component抽象组件;其实现一半是一个抽象类,主要是为了让其子类按照其构造形式传入一个Component抽象组件,这是强制的通用行为(当然,如果系统中装饰逻辑单一,并不需要实现许多装饰器,那么我们可以直接忽略该类,而直接实现一个具体装饰器(ConcreteDecorator)即可);

具体装饰器(ConcreteDecorator):Decorator的具体实现类,理论上,每个ConcreteDecorator都扩展了Component对象的一种功能。

装饰者模式角色分配符合设计模式里氏替换原则、依赖倒置原则,从而使得其具备很强的扩展性,最终满足开闭原则。

3、代理、桥接、装饰器、适配器 4 种设计模式的区别

代理、桥接、装饰器、适配器,这 4 种模式是比较常用的结构型设计模式。它们的代码结构非常相似。笼统来说,它们都可以称为 Wrapper 模式,也就是通过 Wrapper 类二次封装原始类。

尽管代码结构相似,但这 4 种设计模式的用意完全不同,也就是说要解决的问题、应用场景不同,这也是它们的主要区别。这里我就简单说一下它们之间的区别。

代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。

桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。

装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。

适配器模式:适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。

4、装饰者模式的应用场景

装饰者模式在我们生活中应用很广,比如说煎饼加鸡蛋加肠,给蛋糕加水果,给房子装修等等,为对象扩展一些额外的职责。装饰器在代码程序中适用于以下场景:
(1)用于扩展一个类的功能或给一个类添加附加职责。
(2)动态的给一个对象添加功能,这些功能可以再动态的撤销。
(3)需要为一批的兄弟类进行改装或加装功能。

5、装饰者模式和代理模式的对比

我们看一下代理模式的UML类图:

从代理模式的UML类图和通用代码实现上看,代理模式与装饰者模式几乎一模一样。代理模式的Subject对应装饰者模式的Component,代理模式的RealSubject对应装饰者模式的ConcreteComponent,代理模式的Proxy对应装饰器模式的Decorator。确实,从代码实现上看,代理模式的确与装饰者模式是一样的(其实装饰者模式就是代理模式的一个特殊应用),但是这两种设计模式所面向的功能扩展面是不一样的:

装饰者模式强调自身功能的扩展。Decorator所做的就是增强ConcreteComponent的功能(也有可能减弱功能),主体对象为ConcreteComponent,着重类功能的变化;

代理模式强调对代理过程的控制。Proxy完全掌握对RealSubject的访问控制,因此,Proxy可以决定对RealSubject进行功能扩展,功能缩减甚至功能散失(不调用RealSubject方法),主体对象为Proxy;

简单来讲,假设现在小明想租房,那么势必会有一些事务发生:房源搜索、联系房东谈价格……
假设我们按照代理模式进行思考,那么小明只需找到一个房产中介,让他去 干房源搜索,联系房东谈价格这些事情,小明只需等待通知然后付点中介费就行了;
而如果采用装饰器模式进行思考,因为装饰器模式强调的是自身功能扩展,也就是说,如果要找房子,小明自身就要增加房源搜索能力扩展,联系房东谈价格能力扩展,通过相应的装饰器,提升自身能力,一个人做满所有的事情。

6、装饰者模式优缺点

优点:
1.装饰器是继承的有力补充,比继承灵活,不改变原有对象的情况下动态地给一个对象扩展功能,即插即用。
2.通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果。
3.装饰器完全遵守开闭原则。

缺点:
1.会出现更多的代码,更多的类,增加程序复杂性。
2.动态装饰时,多层装饰时会更复杂。

7、抽象装饰器(Decorator)是必需的吗

不是必须的,抽象装饰器的本质就是将附加功能抽离出来,简化原有逻辑,可以根据业务模型,可选择的忽略抽象装饰器。

二、实例1-煎饼

很多小伙伴喜欢逛街吃小吃,此时我们有这样一个需求:一个煎饼摊,煎饼可以加鸡蛋、加香肠,计算最终的价格,我们如何实现?

创建一个煎饼类:

public class Battercake {protected String getMsg(){ return "煎饼";}public int getPrice(){ return 5;}
}

加一个鸡蛋:

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

即加鸡蛋又加香肠:

public class BattercakeWithEggAndSauage extends BattercakeWithEgg {protected String getMsg(){ return super.getMsg() + "+1根香肠";}public int getPrice(){ return super.getPrice() + 2;}}

此时的类继承关系如下:

测试类:

public class Test {public static void main(String[] args) {Battercake battercake = new Battercake();System.out.println(battercake.getMsg() + ",总价:" + battercake.getPrice());BattercakeWithEgg battercakeWithEgg = new BattercakeWithEgg();System.out.println(battercakeWithEgg.getMsg() + ",总价:" + battercakeWithEgg.getPrice());BattercakeWithEggAndSauage battercakeWithEggAndSauage = new BattercakeWithEggAndSauage();System.out.println(battercakeWithEggAndSauage.getMsg() + ",总价:" + battercakeWithEggAndSauage.getPrice());}}

运行结果是没有问题的,但是,如果客户想要只加香肠,或者加辣条等等,那么我们的工作量无疑是很大的。

此时,用装饰器模式可以完美解决这个问题。

使用装饰者模式优化代码

// 煎饼抽象类,或者接口
public abstract class Battercake {protected abstract String getMsg();protected abstract int getPrice();
}
// 煎饼基础套餐(什么也不包装,什么也不加)
public class BaseBattercake extends Battercake{protected String getMsg(){ return "煎饼";}public int getPrice(){ return 5;}}

此时,需要创建一个扩展套餐的抽象装饰器:

public class BattercakeDecorator extends Battercake{private Battercake battercake;public BattercakeDecorator(Battercake battercake) {this.battercake = battercake;}protected String getMsg(){ return this.battercake.getMsg();}public int getPrice(){ return this.battercake.getPrice();}
}
// 鸡蛋装饰器
public class EggDecorator extends BattercakeDecorator{public EggDecorator(Battercake battercake) {super(battercake);}protected String getMsg(){ return super.getMsg() + "1个鸡蛋";}public int getPrice(){ return super.getPrice() + 1;}
}// 香肠装饰器
public class SauageDecorator extends BattercakeDecorator{public SauageDecorator(Battercake battercake) {super(battercake);}protected String getMsg(){ return super.getMsg() + "1根香肠";}public int getPrice(){ return super.getPrice() + 2;}
}
// 测试类
public class Test {public static void main(String[] args) {Battercake battercake;// 基础套餐battercake = new BaseBattercake();// 加鸡蛋battercake = new EggDecorator(battercake);// 再加一个鸡蛋battercake = new EggDecorator(battercake);// 加香肠battercake = new SauageDecorator(battercake);System.out.println(battercake.getMsg() + ",总价" + battercake.getPrice());}
}

三、实例2-日志

假如说我们现有的框架是使用Slf4j+log4j2 实现的,但是现有的日志体系打印出的结果是一段没有任何格式的字符串:

Logger logger = LoggerFactory.getLogger(clazz);
logger.info("测试内容");

我们想将打印的结果转成json格式,就需要采用装饰器模式了。

定义装饰器类,实现顶层Logger接口:

public class LoggerDecorator implements Logger {protected Logger logger;public LoggerDecorator(Logger logger) {this.logger = logger;}public void info(String s) {}// 省略其他实现
}

创建装饰器实现类:

public class JsonLogger extends LoggerDecorator {public JsonLogger(Logger logger) {super(logger);}@Overridepublic void info(String s) {JSONObject result = newJsonObject();result.put("message",s);logger.info(result.toString());}@Overridepublic void error(String s) {JSONObject result = newJsonObject();result.put("message",s);logger.info(result.toString());}@Overridepublic void error(String s, Throwable e){JSONObject result = newJsonObject();result.put("exception",e.getClass().getName());String trace = Arrays.toString(e.getStackTrace());result.put("starckTrace",trace);logger.info(result.toString());}private JSONObject newJsonObject(){return new JSONObject();}
}

在JsonLogger中,对于Logger的各种接口,我们都用JsonObject进行封装,最终还是调用logger.info,只是这个字符串被我们装饰过

定义一个 工厂类,方便使用:

public class JsonLoggerFactory {public static JsonLogger getLogger(Class clazz){Logger logger = LoggerFactory.getLogger(clazz);return new JsonLogger(logger);}
}
public class Test {private static final Logger logger = JsonLoggerFactory.getLogger(Test.class);public static void main(String[] args) {logger.error("系统错误");try {int i = 1/0;} catch (Exception e) {logger.error("异常", e);}}
}

四、JDK中IO流对装饰者模式的使用

IO流中的包装类使用到了装饰者模式。BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter。

我们以BufferedWriter举例来说明,先看看如何使用BufferedWriter

public class Demo {public static void main(String[] args) throws Exception{//创建BufferedWriter对象//创建FileWriter对象FileWriter fw = new FileWriter("C:\\Users\\Think\\Desktop\\a.txt");BufferedWriter bw = new BufferedWriter(fw);//写数据bw.write("hello Buffered");bw.close();}
}

使用起来感觉确实像是装饰者模式,接下来看它们的结构:


BufferedWriter使用装饰者模式对Writer子实现类进行了增强,添加了缓冲区,提高了写数据的效率。

参考资料

http://www.uml.org.cn/sjms/202105262.asp

设计模式之【装饰者模式】,实现“穿衣打扮”自由原来这么简单相关推荐

  1. Java设计模式(装饰者模式-组合模式-外观模式-享元模式)

    Java设计模式Ⅳ 1.装饰者模式 1.1 装饰者模式概述 1.2 代码理解 2.组合模式 2.1 组合模式概述 2.2 代码理解 3.外观模式 3.1 外观模式概述 3.2 代码理解 4.享元模式 ...

  2. 前端也要学系列:设计模式之装饰者模式

    什么是装饰者模式 今天我们来讲另外一个非常实用的设计模式:装饰者模式.这个名字听上去有些莫名其妙,不着急,我们先来记住它的一个别名:包装器模式. 我们记着这两个名字来开始今天的文章. 首先还是上< ...

  3. 设计模式 之 装饰者模式

    2019独角兽企业重金招聘Python工程师标准>>> 设计模式 之 装饰者模式 装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能.它是通过创建一个包装对 ...

  4. 【设计模式】装饰者模式 ( 概念 | 适用场景 | 优缺点 | 与继承对比 | 定义流程 | 运行机制 | 案例分析 )

    文章目录 I . 装饰者模式概念 II . 装饰者模式适用场景 III . 装饰者模式优缺点 IV . 装饰者模式与继承对比 V . 装饰者模式相关设计模式 VI . 装饰者模式四个相关类 VII . ...

  5. 设计模式学习----装饰器模式

    这两天本来是自在学习java collection Framework的Fail Fast底层机制,看到核心的部分时,突然意识到设计模式的问题,上大学到现在我还没有真正理解过设计模式的概念,于是用了大 ...

  6. 【设计模式】装饰器模式的使用

    问题来源 我们在进行软件系统设计的时候,有一些业务(如下图,一些通用的非功能性需求)是多个模块都需要的,是跨越模块的.把它们放到什么地方呢? 最简单的办法就是把这些通用模块的接口写好,让程序员在实现业 ...

  7. C#设计模式(9)——装饰者模式(Decorator Pattern)

    一.引言 在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类).A ...

  8. go设计模式之装饰器模式

    go设计模式之装饰器模式 再写这篇文章时,我已经看了很多其他人发表的类似文章,大概看了这么多吧. 亓斌的设计模式-装饰者模式(Go语言描述) jeanphorn的Golang设计模式之装饰模式 七八月 ...

  9. python中的装饰器、装饰器模式_python 设计模式之装饰器模式 Decorator Pattern

    #写在前面 已经有一个礼拜多没写博客了,因为沉醉在了<妙味>这部小说里,里面讲的是一个厨师苏秒的故事.现实中大部分人不会有她的天分.我喜欢她的性格:总是想着去解决问题,好像从来没有怨天尤人 ...

最新文章

  1. Exchange Server2013 系列十:证书的配置
  2. 为什么叫python编程-中小学生为什么要学Python编程
  3. sitemesh的使用
  4. 计算机技术停滞,究竟什么原因让科技停滞不前呢?
  5. 【Bootstrap】 框架 栅格布局系统设计原理
  6. 主板上的jrgb接口干什么用_用思维导图,解读选配主板的过程,重点解读兼容与接口的搭配技术...
  7. Linux系统简介与准备
  8. mysql对所有id求积_sql 行列式 转换,
  9. 前端向后台发送请求有几种方式?
  10. pyinstaller打包含有openCV库时缺失config文件报错
  11. 在mac上用文本编辑器写java源代码
  12. abaqus帮助文档html,2534-VUMAT用户子程序翻译ABAQUS帮助手册.doc
  13. 【数据库考试】考研复试必备数据库试题
  14. iOS开发--微信和支付宝网页支付(过审, 支付宝支付成功可回跳)
  15. Android 消息通知滚动
  16. 2016上半年高项项目经理考试培训考试感想
  17. 如何避免黑客攻击?国内首个云端加密代码库来帮忙
  18. 自然语言处理--keras实现一维卷积网络对IMDB 电影评论数据集构建情感分类器
  19. `Caché/IRIS` 代码优化效率提升十一条 - 持续更新
  20. 最简真分数c语言,HihoCoder1655 : 第K小最简真分数([Offer收割]编程练习赛39)(唯一分解+容斥定理+二分)(不错的数学题)...

热门文章

  1. 惠惠购物助手android版3.8.2无法安装的原因及解决方法
  2. 技术面试的终极之道,一定是素与简
  3. 事半功倍的高效工具,你值得拥有
  4. python根据日期判断某日是节假日还是工作日chinese_calendar
  5. 分享如何升级macOS Catalina
  6. HCNP路由交换学习指南--- 静态路由
  7. 公主恋人汉化组的事 我出来说明下
  8. OBIEE RPD开发-数据库功能
  9. 02-JavaScript语言
  10. 批量处理图形大小如何更改图片大小尺寸修改图片视频教程ps学习ps教程ps基础新教程