本文原创地址:jsbintask的博客(食用效果最佳),转载请注明出处!

同系列文章: 从未这么明白的设计模式(二):观察者模式

从未这么明白的设计模式(一):单例模式

前言

装饰器模式是为了运行时动态的扩展一个类的功能。它谨遵开闭原则,它实现的关键在于继承和组合的结合使用,解耦对象之间的关系。 各种设计模式学习地址:github.com/jsbintask22…

栗子

首先我们列举一个案例,并且按照面向对象的思想来对应实体之间的关系。

有一个咖啡店,销售各种各样的咖啡,拿铁,卡布奇洛,蓝山咖啡等,在冲泡前,会询问顾客是否要加糖,加奶,加薄荷等。这样不同的咖啡配上不同的调料就会卖出不同的价格。

V1

针对上面的栗子,我们很容易就抽象出对应的实现,如上图。接着,我们就要编写对应的类来实现对应的功能。在这个例子中,主题当然就是咖啡,并且它有一个属性是名字,一个行为 价格,出于“面向对象”的思想,我们自然会设计出抽象类Coffee:

public abstract class Coffee {/*** 获取咖啡得名字*/public abstract String getName();/*** 获取咖啡的价格*/public abstract double getPrice();
}
复制代码

接着,按照继承的思想,我们要开始设计出具体的实现类,因为拿铁,卡布奇洛,蓝山搭配上不同的调料(上面三种)会有不同的价格,名字,所以我们至少得设计出 3 X 3 = 9 个类来分别对应它们的名字和价格:

嗯!我想不用说这样设计得缺陷也很明显了! 由于不同的咖啡和不同的调料得各种任意组合,使得出现了类爆炸的现象。既然有这么明显的缺陷,那我们当然得改! 我们可以考虑把各种调料当作属性加入到Coffee这个抽象类中,接着在实现类中计算价格和名字时,分别判断是否加入了各种调料包,得到不同的名字和价格!

按照上面的思想,我们的Coffee类现在变成了这样:

public abstract class Coffee {// 是否加了牛奶protected boolean addedMilk;// 是否加了糖protected boolean addedSugar;// 是否加了薄荷protected boolean addedMint;/*** 获取咖啡得名字*/public abstract String getName();/*** 获取咖啡的价格*/public abstract double getPrice();
}
复制代码

接着,我们实现一种咖啡,蓝山咖啡:

public class BuleCoffee extends Coffee {@Overridepublic String getName() {StringBuilder name = new StringBuilder();name.append("蓝山");if (addedMilk) {name.append("牛奶");}if (addedMilk) {name.append("薄荷");}if (addedSugar) {name.append("加糖");}return name.toString();}@Overridepublic double getPrice() {double price = 10;if (addedMilk) {price += 1.1;}if (addedMilk) {price += 3.2;}if (addedSugar) {price += 2.7;}return price;}
}
复制代码

嗯!现在似乎比上面愉快多了。其实不然!我们仔细分析这种设计,会发现它似乎不太符合”封装的思想“,比如说针对拿铁,对于加薄荷而言,对他总是多余的! 而对于蓝山而言,牛奶又显得很多余! 所以这种设计也并不合理。 另外,我们假设coffee,拿铁等实体类来自第三方类库,我们并不能改动这些类的实现, 又要怎么得到名字和价格呢?

这个时候,我们就得使用装饰器模式来动态的扩展类行为! 所以我们设计出V3版本。

V3

开闭原则

首先,我们需要了解一个面向对象的一个基本设计原则:开闭原则,它指的是类应该对修改关闭,对扩展开放

怎么理解呢? 就比如我们上方说的:假如cofee和它的一众实现拿铁,卡布奇洛,蓝山来自第三方类库,并且这个类库已经很”适合“,”实用“了。 而我们为了得到加入不同调料的咖啡的名字和价格,我们就得修改这些实现,而这样的修改,总是免不了稳定性的改变。对原本的系统来说也是一种风险! 所以我们应该 对修改关闭,对扩展开放;

继承和组合

遵循开闭原则,那我们就得对外扩展,那怎么对外扩展呢? 这也是装饰器模式实现的关键,利用继承和组合的结合; 现在我们可以考虑设计出一个装饰类,它也继承自coffee,并且它内部有一个coffee的实例对象:

现在,我们多了一个咖啡装饰器: CoffeeDecorator:

public abstract class CoffeeDecorator implements Coffee {private Coffee delegate;public CoffeeDecorator(Coffee coffee) {this.delegate = coffee;}@Overridepublic String getName() {return delegate.getName();}@Overridepublic double getPrice() {return delegate.getPrice();}
}
复制代码

接着,我们将牛奶,薄荷作为抽象出一个类,继承自CoffeeDecorator,所以,现在类图就成了这样:

我们实现一个MilkCoffeeDecorator

public class MilkCoffeeDecorator extends CoffeeDecorator {public MilkCoffeeDecorator(Coffee coffee) {super(coffee);}@Overridepublic String getName() {return "牛奶, " + super.getName();}@Overridepublic double getPrice() {return 1.1 + super.getPrice();}
}
复制代码

按同样的方法可以实现出MintCoffeeDecoratorSugarCoffeeDecorator。接着我们写一个测试类:

public class App {public static void main(String[] args) {// 得到一杯原始的蓝山咖啡Coffee blueCoffee = new BlueCoffee();System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());// 加入牛奶blueCoffee = new MilkCoffeeDecorator(blueCoffee);System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());// 再加入薄荷blueCoffee = new MintCoffeeDecorator(blueCoffee);System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());// 再加入糖blueCoffee = new SugarCoffeeDecorator(blueCoffee);System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());}
}
复制代码

从结果我们可以看出,随着不断加入各种调料,价格,名字都在改变! 这说明我们加入不同的调料,动态的改变了咖啡的名字和价格!

思考

从上面的最后的装饰器模式的实现来看,我们可以得出以下结论:

  1. 通过装饰器模式可以动态的将责任附加到原有的对象上,而不改变原有的code。
  2. 遵循开闭原则
  3. 装饰者和被装饰者有相同的父类(如栗子中的Coffee)
  4. 可以用多个装饰器装饰同一个对象。(见运行类)
  5. 装饰者可以在被装饰者的行为之前或之后动态的加上自己的行为。(参考装饰实现)
  6. 组合比继承更加的灵活(上面的coffee代理)

扩展

到现在,我们已经实现了一个自己的装饰器,我们来看看jdk中用到的装饰器实现.

IO

我们可以查看FilterInputStream:

它的主要是实现者为BufferedInputStream: 所以我们经常可以使用BufferedInputStream装饰一个InputStream,比如FileInputStream: new BufferedInputStream(FileInputStream); 这就是装饰器模式的典型应用。

tomcat

在tomcat的HttpServletRequest的内部实现代码中,RequestFacde继承自HttpServlet,而它内部的实现也是通过代理Request对象,而Request对象继承自HttpServlet,Request内部代理了org.apache.coyote.Request来实现的。

总结

装饰器模式充分展示了组合的灵活。利用它来实现扩展。它同时也是开闭原则的体现。 如果相对某个类实现运行时功能动态的扩展。 这个时候你就可以考虑使用装饰者模式!

关注我,这里只有干货!

从未这么明白的设计模式(三):装饰器模式相关推荐

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

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

  2. 设计模式之装饰器模式详解

    设计模式之装饰器模式详解 文章目录 设计模式之装饰器模式详解 一.什么是装饰器模式 二.装饰器模式的角色组成 三.装饰器模式通用写法示例 四.装饰器模式业务中的应用举例 五.装饰器模式优缺点 一.什么 ...

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

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

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

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

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

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

  6. python 设计模式之装饰器模式 Decorator Pattern

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

  7. 零基础学习设计模式之装饰器模式(配套视频)

    零基础学习设计模式之装饰器模式 定义 在不改变目标结构的情况下,动态的给对象增加功能 举例 如房子装修.相片加相框等,都是装饰器模式. 基本组件 抽象构件(Component)角色:定义一个抽象接口以 ...

  8. 详解设计模式:装饰器模式

    装饰器模式(Decorator Pattern)也称为包装模式(Wrapper Pattern),是 GoF 的 23 种设计模式中的一种结构型设计模式.装饰器模式 是指在不改变原有对象的基础之上,将 ...

  9. javascript设计模式之装饰器模式(结构型模式)

    javascript设计模式之装饰器模式 js的设计模式分为创建型模式,结构型模式和行为模式 结构模式描述了如何组合对象以提供新的功能. 装饰器模式是一种常见的结构型模式,我们可以以一个基础对象为基础 ...

  10. 【设计模式】--- 装饰器模式、静态代理模式和动态代理模式

    文章目录 1 引子 2 业务场景介绍 3 静态代理模式 4 装饰器模式 5 动态代理模式 5.1 Proxy --- 具体的代理对象生成组件 5.2 InvocationHandler --- 封装被 ...

最新文章

  1. 实现ABAP条件断点的三种方式
  2. 使用域超级管理员打开Exchange 2010发现没有权限
  3. Python3 循环
  4. Spring Cloud —— 链路追踪技术
  5. C++中固定长度短字符串比较是否相同,忽略大小写比对时的小技巧
  6. 动态规划 —— 线性 DP —— 序列问题
  7. STM32工作笔记0068---SPI同步通信Flash读写实验
  8. 130.C++经典面试题 52-100
  9. vb读取mysql数据库数据_VB读取ORACLE数据库的两种方法
  10. python 下载安装 教程
  11. oppor9splus科学计算机,oppo r9s plus手机驱动
  12. MongoDB下载安装教程(Windows)
  13. FireFox浏览器渗透测试插件
  14. 用spark实现单词统计
  15. 《计算广告》读书笔记——第一章 在线广告综述
  16. nrm 切换不同的源工具
  17. HC05蓝牙模块与手机APP连接
  18. ipmitool使用手册
  19. C语言的三种参数传递方式
  20. SuperMap之等高线制作

热门文章

  1. vue 默认加载某一子路由
  2. 与网友“阵春风”交流
  3. 沙漠求生十五选五实验
  4. 【马红“名师+”研修共同体】“课” 展风采,“研”无止境----教学交流活动(二)
  5. [网络安全自学篇] 七十五.Vulnhub靶机渗透之bulldog信息收集和nc反弹shell(三)
  6. 第二节 控制系统的数学模型——传递函数
  7. JAVA-多线程 三 {多线程状态}JAVA从基础开始 -- 3
  8. 上天入地无所不能的C语言(一)
  9. 计算机excel界面,2017《职称计算机》知识点:Excel用户界面
  10. matlab怎样编程形成软件_Matlab编程笔记之GUI程序转exe