定义

在不改变原有对象的基础上,将功能附加到原有功能上,进行功能的扩展,
动态地给一个对象增加一些额外的功能,装饰模式比生成子类(继承)实现更为灵活。
在装饰者模式中,为了让系统具有更好的灵活性和可扩展性,我们通常会定义一个抽象装饰类,而将具体的装饰类作为它的子类

类型

结构型

UML图

装饰者(Decorator)和具体组件(ConcreteComponent)都继承自组件(Component),具体组件的方法实现不需要依赖于其它对象,而装饰者组合了一个组件,这样它可以装饰其它装饰者或者具体组件。所谓装饰,就是把这个装饰者套在被装饰者之上,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最低层,因为只有具体组件的方法实现不需要依赖于其它对象。

角色

Component(抽象组件):它是具体组件和抽象装饰类的共同父类,声明了在具体组件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。

ConcreteComponent(具体组件):它是抽象组件类的子类,用于定义具体的组件对象,实现了在抽象组件中声明的方法,装饰器可以给它增加额外的职责(方法)。

Decorator(抽象装饰类):它也是抽象组件类的子类,用于给具体组件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象组件对象的引用,通过该引用可以调用装饰之前组件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。 对于Component(抽象组件)来说,是无需了解Decorator(抽象装饰类)的存在的,Component(抽象组件)只顾做好自己份内的事就行。

ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向组件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。

由于具体组件类和装饰类都实现了相同的抽象组件接口,因此装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。

装饰模式的核心在于抽象装饰类的设计。

示例:

version1:通过继承给一个类添加功能

煎饼类(实现基本的功能)

public class Battercake {protected String getDesc(){return "煎饼";}protected int cost(){return 8;}}

加鸡蛋的煎饼类(通过继承给煎饼类添加鸡蛋)

public class BattercakeWithEgg extends Battercake {@Overridepublic String getDesc() {return super.getDesc()+" 加一个鸡蛋";}@Overridepublic int cost() {return super.cost()+1;}}

加鸡蛋和香肠的煎饼类(使用继承继续添加新功能)

public class BattercakeWithEggSausage extends BattercakeWithEgg {@Overridepublic String getDesc() {return super.getDesc()+ " 加一根香肠";}@Overridepublic int cost() {return super.cost()+2;}
}

Test类

public class Test {public static void main(String[] args) {Battercake battercake = new Battercake();System.out.println(battercake.getDesc()+" 销售价格:"+battercake.cost());Battercake battercakeWithEgg = new BattercakeWithEgg();System.out.println(battercakeWithEgg.getDesc()+" 销售价格:"+battercakeWithEgg.cost());Battercake battercakeWithEggSausage = new BattercakeWithEggSausage();System.out.println(battercakeWithEggSausage.getDesc()+" 销售价格:"+battercakeWithEggSausage.cost());}
}

version2:通过装饰者模式

抽象组件(抽象煎饼类)

public abstract class ABattercake {protected abstract String getDesc();protected abstract int cost();}

具体组件(煎饼类)

public class Battercake extends ABattercake {@Overrideprotected String getDesc() {return "煎饼";}@Overrideprotected int cost() {return 8;}
}

抽象装饰类,抽象装饰类通过成员属性的方式将煎饼抽象类组合进来,同时也继承了煎饼抽象类,且这里添加了新功能 doSomething()

public abstract class AbstractDecorator extends ABattercake {private ABattercake aBattercake;public AbstractDecorator(ABattercake aBattercake) {this.aBattercake = aBattercake;}protected abstract void doSomething();@Overrideprotected String getDesc() {return this.aBattercake.getDesc();}@Overrideprotected int cost() {return this.aBattercake.cost();}
}

鸡蛋装饰器,继承了抽象装饰类,鸡蛋装饰器在父类的基础上增加了一个鸡蛋,同时价格加上 1 块钱

public class EggDecorator extends AbstractDecorator {public EggDecorator(ABattercake aBattercake) {super(aBattercake);}@Overrideprotected void doSomething() {System.out.println("准备好,我要添加鸡蛋了");}@Overrideprotected String getDesc() {return super.getDesc()+" 加一个鸡蛋";}@Overrideprotected int cost() {return super.cost()+1;}protected void dance(){System.out.println("看我一边跳舞,一边加鸡蛋");}}

香肠装饰器,与鸡蛋装饰器类似,继承了抽象装饰类,给在父类的基础上加上一根香肠,同时价格增加 2 块钱

public class SausageDecorator extends AbstractDecorator {public SausageDecorator(ABattercake aBattercake) {super(aBattercake);}@Overrideprotected void doSomething() {System.out.println("准备好了没有,我要开始加香肠了");}@Overrideprotected String getDesc() {return super.getDesc()+" 加一根香肠";}@Overrideprotected int cost() {return super.cost()+2;}protected void sing(){System.out.println("are you ok? 我要开始加香肠了,烤面筋啊我的烤面筋,搞错了,再来");}
}

测试Test

public class Test {public static void main(String[] args) {ABattercake aBattercake;aBattercake = new Battercake();System.out.println(aBattercake.getDesc()+" 销售价格:"+aBattercake.cost());aBattercake = new EggDecorator(aBattercake);System.out.println(aBattercake.getDesc()+" 销售价格:"+aBattercake.cost());//        System.out.println(aBattercake.doSomething());
//        System.out.println(aBattercake.dance());aBattercake = new SausageDecorator(aBattercake);System.out.println(aBattercake.getDesc()+" 销售价格:"+aBattercake.cost());//半透明装饰模式EggDecorator eggDecorator = new EggDecorator(aBattercake);eggDecorator.doSomething();eggDecorator.dance();SausageDecorator sausageDecorator = new SausageDecorator(aBattercake);sausageDecorator.doSomething();sausageDecorator.sing();//        AbstractDecorator abstractDecorator = new EggDecorator(aBattercake);
//        abstractDecorator.doSomething();
//        abstractDecorator.dance();}
}

煎饼类对象还是那个煎饼类对象,但是会有装饰类对象来给他增加新东西,并且各个装饰类对象的装饰并没有先后关系,都是直接装饰原始煎饼类对象。多个装饰如果有先后依赖关系(比如存储数据之前,做一下数据的过滤和加密。如果先加密了数据,那就不好过滤数据了,要先过滤数据,再对数据进行加密),那就要注意装饰顺序了。不过最好将装饰的东西之间设计成独立的,没有先后依赖关系,能以任何顺序进行组合。

透明装饰模式和半透明装饰模式

1.透明装饰模式

在透明装饰模式中要求客户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该将对象声明为具体组件类型或具体装饰类型,而应该全部声明为抽象组件类型。对客户端而言,具体组件类和具体装饰类对象没有任何区别。(缺点,无法单独调用装饰类的独有功能)

上面的例子就是透明装饰模式,装饰后的对象都是通过抽象组间类类型 ABattercake 的变量来引用的,然后通过组合方式注入到其他具体装饰对象实例里面,但是这样的话就只能调用抽象组间类里面已经定义的方法(具体装饰类针对这些定义的方法做了一些装饰)

ABattercake aBattercake;
aBattercake = new EggDecorator(aBattercake);

2.半透明装饰模式

用具体装饰类型来定义装饰后的对象,而具体组件类型仍然可以使用抽象组件类型来定义,可以单独调用装饰的独有方法。(缺点:无法多次进行装饰)

透明装饰模式的设计难度较大,而且有时我们需要单独调用新增的业务方法。为了能够调用到新增方法,我们不得不用具体装饰类型来定义装饰之后的对象,而具体组件类型还是可以使用抽象组件类型来定义,这种装饰模式即为半透明装饰模式。

半透明装饰模式可以给系统带来更多的灵活性,设计相对简单,使用起来也非常方便;但是其最大的缺点在于不能实现对同一个对象的多次装饰,而且客户端需要有区别地对待装饰之前的对象和装饰之后的对象。

ABattercake aBattercake;
EggDecorator eggDecorator = new EggDecorator(aBattercake);
eggDecorator.doSomething();
eggDecorator.dance();SausageDecorator sausageDecorator = new SausageDecorator(aBattercake);
sausageDecorator.doSomething();
sausageDecorator.sing();

装饰者模式总结

优点

  • 通过使用不同装饰类以及这些类的排列组合,可以实现不同的效果。
  • 装饰者模式和继承关系的目的都是要扩展原有对象的功能,但是装饰者模式可以提供比继承更多的灵活型,不会导致类的个数急剧增加
  • 类应该对扩展开放,对修改关闭(符合开闭原则): 具体组件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体组件类和具体装饰类,原有类库代码无须改变。
  • 具体组件类的职责更加清晰简洁,有效的把类的核心职责和装饰功能区分开,不会产生很多冗余的功能,简化了原有的类,只有在需要某功能的时候,才由装饰者装饰添加功能。并且这样也能去除相关类中重复的装饰逻辑

缺点

  • 更多的代码,更多的类,增加程序的复杂性。
  • 动态装饰时、多层装饰时会使系统更复杂。
  • 越灵活也意味着更容易出错。装饰模式提供了一种比继承更加灵活机动的解决方案,但同时也意味着比继承更加易于出错,排错也更困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐
  • 使用装饰模式进行系统设计时将产生很多小对象(大对象包小对象,通过组合的形式将小对象注入到大对象里面),大量小对象的产生势必会占用更多的系统资源,在一定程序上影响程序的性能。

适用场景

  • 扩展一个类的功能或者给一个类添加附加职责
  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
  • 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销
  • 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种扩展或者扩展之间的组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类已定义为不能被继承(如Java语言中的final类)。

相关设计模式比较

  • 装饰者模式和代理模式: 装饰者模式关注的是对象功能的动态添加。而代理模式关注的是对对象的控制访问,对它的用户隐藏对象的具体信息。
  • 装饰者模式和适配器模式:装饰者模式和被装饰的类要实现同一个接口,或者装饰类是被装饰的类的子类。 适配器模式和被适配的类具有不同的接口。

应用:

Java I/O中的装饰者模式


由上图可知在Java中应用程序通过输入流(InputStream)的Read方法从源地址处读取字节,然后通过输出流(OutputStream)的Write方法将流写入到目的地址。流的来源主要有三种:
①本地的文件(File)
②控制台输入
③通过socket实现的网络通信

我们可以通过其他博主的图片看一看java IO的装饰者模式,
下面的图可以看出Java中的装饰者类和被装饰者类以及它们之间的关系,这里只列出了InputStream中的关系:

由上图可以看出只要继承了FilterInputStream的类就是装饰者类,可以用于包装其他的流,装饰者类还可以对装饰者和类进行再包装

如下代码是使用的半透明装饰模式

public class StreanDemo {public static void main(String[] args) throws IOException {DataInputStream in=new DataInputStream(new BufferedInputStream(new FileInputStream("D:\\JAVAworkspace\\ProgramTest\\src\\StreamDemo.java")));while(in.available()!=0){System.out.print((char)in.readByte());}in.close();}}

上面程序中对流进行了两次包装,先用 BufferedInputStream将FileInputStream包装成缓冲流也就是给FileInputStream增加缓冲功能,再DataInputStream进一步包装方便数据处理。

自定义装饰类
同理,你可以继承抽象装饰类,来实现自己定义的具体装饰类
如下代码:将输入流中的所有大写字母变成小写字母

public class LowerCaseInputStream extends FilterInputStream {protected LowerCaseInputStream(InputStream in) {super(in);}@Overridepublic int read() throws IOException {int c=super.read();return (c==-1?c:Character.toLowerCase(c));}@Overridepublic int read(byte[] b, int off, int len) throws IOException {int result=super.read(b, off, len);for(int i=off;i<off+result;i++){b[i]=(byte)Character.toUpperCase((char)b[i]);}return  result;}public static void main(String[] args) throws IOException {int c;try (InputStream in = new LowerCaseInputStream(new FileInputStream(文件路径))) {try {while ((c = in.read()) >= 0) {System.out.print((char) c);}} finally {in.close();}}}}

结果展示

几种常用流的应用场景:

流名称 应用场景
ByteArrayInputStream 访问数组,把内存中的一个缓冲区作为 InputStream 使用,CPU从缓存区读取数据比从存储介质的速率快10倍以上
StringBufferInputStream 把一个 String 对象作为。InputStream。不建议使用,在转换字符的问题上有缺陷
FileInputStream 访问文件,把一个文件作为 InputStream ,实现对文件的读取操作
PipedInputStream 访问管道,主要在线程中使用,一个线程通过管道输出流发送数据,而另一个线程通过管道输入流读取数据,这样可实现两个线程间的通讯
SequenceInputStream 把多个 InputStream 合并为一个 InputStream . “序列输入流”类允许应用程序把几个输入流连续地合并起来
DataInputStream 特殊流,读各种基本类型数据,如byte、int、String的功能
ObjectInputStream 对象流,读对象的功能
PushBackInputStream 推回输入流,可以把读取进来的某些数据重新回退到输入流的缓冲区之中
BufferedInputStream 缓冲流,增加了缓冲功能

References:

  • https://coding.imooc.com/class/270.html
  • https://www.pdai.tech/md/dev-spec/pattern/12_decorator.html
  • https://whirlys.blog.csdn.net/article/details/82764333
  • https://blog.csdn.net/u013309870/article/details/75735676
  • 《大话设计模式》
  • https://blog.csdn.net/qq_37960603/article/details/104087989
  • https://blog.csdn.net/weixin_44045828/article/details/110490324

(写博客主要是对自己学习的归纳整理,资料大部分来源于书籍和网络资料,整理不易,但是难免有不足之处,如有错误,请大家评论区批评指正。同时感谢广大博主和广大作者辛苦整理出来的资源。)

设计模式之装饰者模式相关推荐

  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

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

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

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

最新文章

  1. 两条线段相切弧_两条直线间的圆弧连接
  2. 机器视觉系统原理及学习策略
  3. 美团外卖美食知识图谱的迭代及应用
  4. 进程、线程、多线程、并发、并行学习记录
  5. Catch Me If You ... Can't Do Otherwise--转载
  6. 本地运行hadoop
  7. COM+在win2003+IIS+MSSQL环境下的部署步骤
  8. mysql重置增量_摆脱困境:在每种测试方法之前重置自动增量列
  9. 从Java集成Active Directory
  10. Bootstrap3 Font Awesome 字体图标
  11. Flink : Flink run yarn 报错 could not build the program from jar file -ynm
  12. python db.commit_python对MySQL进行数据的插入、更新和删除之后需要commit,数据库才会真的有数据操作。(待日后更新)...
  13. 整流3-前级三相PWM整流器软件层面理解
  14. Total Control通过定义配置文件,预加载脚本教程
  15. 肯德尔(Kendall)相关系数概述及Python计算例
  16. 30分钟java桌球小游戏,30分钟完成桌球小游戏项目
  17. 适合前端Vue开发童鞋的跨平台Weex
  18. 神州优车拟41亿元收购宝沃汽车67%股权 1
  19. openwrt 软件安装依赖冲突
  20. ceph osd为down的情况

热门文章

  1. cad图纸怎么看懂_快速看懂cad图纸的教程全解
  2. 从零开始学WEB前端——HTML实战练习
  3. Lua程序设计任务系统和NPC
  4. 百宝云网络验证对接+脚本更新功能(源码)
  5. python设计麻将_python麻将和牌算法
  6. Ubuntu截图快捷键
  7. 和平精英灵敏度分享码服务器没有响应,和平精英灵敏度分享码怎么弄 灵敏度分享码怎么用...
  8. matlab马赫带,学习实现马赫带效果
  9. DOM是什么?(超详细解释)
  10. java做微信支付notify_url异步通知服务端的写法