设计模式(三)_装饰器模式 - Kevin_zheng

zhenghengbin 2021-11-04 原文

上篇学习了策略模式,现在回想下,什么是策略模式,好了。本篇主要介绍装饰器模式,just do it!

装饰器模式指的是动态的将责任附加到对象上。若要扩展功能,装饰器模式提供了比继承更弹性的替代方案。

老王来到商场买衣服,需要买衣服,裤子,帽子……

  1. public class Wang {
  2. public void show(){
  3. System.out.println("我穿上衣服,累计花费100元");
  4. System.out.println("我穿上裤子,累计花费250元");
  5. System.out.println("我穿上帽子,花费300元");
  6. }
  7. }

如果老王每新买一件衣服,都要修改一下这个show方法,这就不符合开闭原则。我们可以用装饰器模式。针对这个过程,我画了老王买衣服的uml图
enter image description here

观察上图,观察模式主要有4个角色

  • 抽象构件角色(给出一个抽象接口Person,以规范准备接受附加责任的对象)
  • 装饰器父类
  • 具体的装饰器对象
  • 被装饰的对象

老王就是被装饰的对象,衣服帽子 就是装饰器
从图中可以看出,装饰器和被装饰的2个特点,也是装饰器模式的关键

  • 装饰器和被装饰对象实现同一个接口;
  • 装饰器中使用了被装饰的对象

代码实现
(1) 抽象构件角色

  1. public interface Person {
  2. /**
  3. * 累积消费
  4. * @return
  5. */
  6. public double cost();
  7. public void show();
  8. }

(2).老王,被装饰的对象

  1. public class Wang implements Person {
  2. @Override
  3. public double cost() {
  4. return 0;
  5. }
  6. @Override
  7. public void show() {
  8. System.out.println("我是赤裸裸的老王");
  9. }
  10. }

(3).装饰器父类,和被装饰对象实现同一个接口Person

  1. public abstract class ClothesDecorator implements Person {
  2. protected Person person;
  3. public ClothesDecorator(Person person){
  4. this.person = person;
  5. }
  6. }

(4) 具体的装饰器类:衣服和帽子

  1. public class Jacket extends ClothesDecorator {
  2. public Jacket(Person person){
  3. super(person);
  4. }
  5. @Override
  6. public double cost() {
  7. return person.cost()+100;
  8. }
  9. @Override
  10. public void show() {
  11. person.show();
  12. System.out.println("买了夹克,累计花了"+this.cost());
  13. }
  14. }
  15. public class Hat extends ClothesDecorator{
  16. public Hat(Person person) {
  17. super(person);
  18. }
  19. @Override
  20. public double cost() {
  21. return person.cost()+50;
  22. }
  23. @Override
  24. public void show() {
  25. person.show();
  26. System.out.println("买了帽子,累计花了"+this.cost());
  27. }
  28. }

测试

  1. Person wang = new Wang();
  2. wang = new Jacket(wang);
  3. wang = new Hat(wang);
  4. // wang = new Hat(new Jacket(wang));
  5. wang.show();
  6. System.out.println("买单:王总共消费"+wang.cost());

输出结果

  1. 我是赤裸裸的老王
  2. 买了夹克,累计花了100.0
  3. 买了帽子,累计花了150.0
  4. 买单:王总共消费150.0

如果还要买鞋子,只要动态创建鞋子的装饰类,就可以了,不用修改已经写好的类。也贯彻了开闭原则。

使用装饰器模式的关键点

  • 装饰器和被装饰对象实现同一个接口,实际开发中也可能使用继承。
  • 装饰器中的方法可以调用被装饰对象提供的方法,以此实现功能累加的效果,例如,夹克装饰器和帽子装饰器中调用了 person.cost() + xx 实现累计消费金额的累加。

装饰器模式在Java体系中的经典应用是Java I/O,下面讲解字节输入流InputStream
我简单画了UMl图,并用颜色进行了标注

enter image description here

定义中说:“装饰器提供了比继承更有弹性的解决方案”,为甚么这样说?

  • InputStream假设这里写了两个实现类,FileInputStream,ObjectInputStream分别表示文件字节输入流,对象字节输入流
  • 现在我要给这两个输入流加入一点缓冲功能以提高输入流效率,使用继承的方式,那么就写一个BufferedInputStream,继承FileInputStream,ObjectInputStream,给它们加功能
  • 现在我有另外一个需求,需要给这两个输入流加入一点网络功能,那么就写一个SocketInputStream,继承继承FileInputStream,ObjectInputStream,给它们加功能。

这样就导致2个问题
1)、因为我要给哪个类加功能就必须继承它,比如我要给FileInputStream,ObjectInputStream加上缓冲功能、网络功能就得扩展出2*2=4个类,更多的以此类推,这样势必导致类数量不断膨胀
2)、代码无法复用,给FileInputStream,ObjectInputStream加入缓冲功能,本身代码应该是一样的,现在却必须继承完毕后把一样的代码重写一遍,多此一举,代码修改的时候必须修改多个地方,可维护性很差

所以,这个的时候我们就想到了一种解决方案:

  • 在要扩展的类比如BufferedInputStream中持有一个InputStream的引用,在BufferedInputStream调用InputStream中的方法,这样扩展的代码就可以复用起来.

  • 将BufferedInputStream作为InputStream的子类,这样客户端只知道我用的是InputStream而不需要关心具体实现,可以在客户端不知情的情况下,扩展InputStream的功能,加上缓冲功能
    这就是装饰器模式简单的由来,一切都是为了解决实际问题而诞生

代码分析

1、InputStream是一个抽象构件角色

  1. public abstract class InputStream implements Closeable {
  2. // MAX_SKIP_BUFFER_SIZE is used to determine the maximum buffer size to
  3. // use when skipping.
  4. private static final int MAX_SKIP_BUFFER_SIZE = 2048;
  5. ...
  6. }

2、被装饰对象ByteArrayInputStream

  1. public
  2. class ByteArrayInputStream extends InputStream {
  3. /**
  4. * An array of bytes that was provided
  5. * by the creator of the stream. Elements <code>buf[0]</code>
  6. * through <code>buf[count-1]</code> are the
  7. * only bytes that can ever be read from the
  8. * stream; element <code>buf[pos]</code> is
  9. * the next byte to be read.
  10. */
  11. protected byte buf[];
  12. ...
  13. }

3.装饰器父类FilterInputStream

  1. public
  2. class FilterInputStream extends InputStream {
  3. /**
  4. * The input stream to be filtered.
  5. */
  6. protected volatile InputStream in;

4、具体装饰类BufferedInputStream

  1. public
  2. class BufferedInputStream extends FilterInputStream {
  3. private static int DEFAULT_BUFFER_SIZE = 8192;
  4. /**
  5. * The maximum size of array to allocate.
  6. * Some VMs reserve some header words in an array.
  7. * Attempts to allocate larger arrays may result in
  8. * OutOfMemoryError: Requested array size exceeds VM limit
  9. */
  10. private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;

具体测试

  1. public static void main(String[] args) throws Exception
  2. {
  3. File file = new File("src/test.txt");
  4. InputStream in0 = new FileInputStream(file);
  5. InputStream in1 = new BufferedInputStream(new FileInputStream(file));
  6. InputStream in2 = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
  7. }

我们这里实例化出了三个InputStream的实现类:

  • in0这个引用指向的是new出来的FileInputStream,这里简单构造出了一个文件字节输入流
  • in1这个引用指向的是new出来的BufferedInputStream,它给FileInputStream增加了缓冲功能,使得FileInputStream读取文件的内容保存在内存中,以提高读取的功能
  • in2这个引用指向的是new出来的DataInputStream,它也给FileInputStream增加了功能,因为它有DataInputStream和BufferedInputStream两个附加的功能
  • 对于半透明装饰器模式,装饰后的类未必有和抽象构件角色同样的接口方法,它可以有自己扩展的方法
  • 对于全透明装饰器模式,装饰后的类有着和抽象构件角色同样的接口方法

如下面的代码,就是全透明装饰器模式

  1. File file = new File("src/test.txt");
  2. InputStream in0 = new FileInputStream(file);
  3. InputStream in1 = new BufferedInputStream(new FileInputStream(file));

半透明装饰器模式则是

  1. FileInputStream in3 = new FileInputStream(file);
  2. BufferedInputStream in4 = new BufferedInputStream(new FileInputStream(file));
  3. DataInputStream in5 = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));

全透明装饰器模式是一种比较理想主义的想法,现实中不太可能出现。

比如BufferedInputStream吧,我把FileInputStream装饰为BufferedInputStream,难道BufferedInputStream就完全没有自己的行为?

装饰器模式的作用是动态给对象增加一些功能,而不需要修改对象本身。

根据装饰器模式,我们自定义一个装饰器,将所有的字母转成空格

src/src/test.txt 文本内容

  1. hello java78879

装饰类

  1. public class CharacterInputStream extends FilterInputStream {
  2. public CharacterInputStream(InputStream in) {
  3. super(in);
  4. }
  5. @Override
  6. public int read() throws IOException {
  7. //ASCLL码对照,[97,122] 和 [65,90]是英文字母
  8. int c = super.read();
  9. if(c >= 97 && c <= 122 || c >= 65 && c <= 90){
  10. return 32; //32是空格
  11. }else{
  12. return c;
  13. }
  14. }
  15. }

测试

  1. DataInputStream in = new DataInputStream(
  2. new CharacterInputStream(
  3. new FileInputStream("src/test.txt")));
  4. String str;
  5. while((str = in.readLine()) != null){
  6. System.out.println(str);
  7. }

测试结果

  1. 78879
  • 扩展功能的方式比较灵活;
    • 装饰器模式与继承关系的目的都是要扩展对象的功能,但是装饰器模式可以提供比继承更多的灵活性。装饰器模式允许系统动态决定贴上一个需要的装饰,或者除掉一个不需要的装饰。继承关系是不同,继承关系是静态的,它在系统运行前就决定了
  • 每一个装饰器相互独立,需要修改时不会互相影响。
  • 多层装饰比较复杂,就像 Java IO 流,对于初学者不友好。

代码下载 github

发表于
2018-06-24 11:39 
Kevin_zheng 
阅读(295
评论(0
编辑 
收藏 
举报

 

版权声明:本文为zhenghengbin原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/zhenghengbin/p/9220007.html

设计模式(三)_装饰器模式 - Kevin_zheng的更多相关文章

  1. 网站推广优化(SEO,网站关键字优化,怎么优化网站,如何优化网站关键字) – 爱编程的欧巴

      网站推广优化教程100条(完整版)下面介绍新手建站推广完美教程,各位根据自己的实际情况酌情选用: 1、准备 […]...

  2. window中磁盘空间不足但是找不到使用空间的文件 – 淡淡人生过

    window中磁盘空间不足但是找不到使用空间的文件 今天看到 电脑的  d盘 空间爆红,空间满了,去找了找没有 […]...

  3. DAX/PowerBI系列 – 关于时间系列 – 时间相关数值比较 – 用非自带函数

    DAX/PowerBI系列 – 关于时间系列 – 时间相关数值比较 – 用 […]...

  4. 【Java】equals 和 == 的区别

    之前有在 Java字符串比较(3种方法)以及对比 C++ 时的注意项 中写过一点关于 equals()与==的 […]...

  5. [#] – .Net平台的实时性能监控

    App Metricshttps://www.app-metrics.io ASP.NET Core之跨平台的 […]...

  6. 前端直播功能开发总结 – 淡忘&天涯

    前端直播功能开发总结 最近公司要开发一个直播功能,自己也是研究了很久,这里总结一下: 这里直播还是用的第三方的 […]...

  7. GitHub入门之一:使用github下载项目 .

    git作为目前比较流行的版本控制系统,被各个互联网公司广泛使用着。目前国外的网站有GitHub,国内的有CSD […]...

  8. EL表达式中 字符串与数字的比较 – shake

    EL表达式中 字符串与数字的比较    <option value="01" ${param.tjMon […]...

随机推荐

  1. 轻松搭建CAS 5.x系列(4)-Java客户端程序接入CAS单点登录,Hello World版

    概述说明 按照本系列的前3篇文章描述的步骤,我们已经搭建好cas sso server。那应用程序怎么接入到实 […]...

  2. Vue.js 入门

      1、必须引入vue.js 2、vue.js 语法   v-on 用于事件     e.g:  <bu […]...

  3. 19种损失函数,你能认识几个?

    19种损失函数,你能认识几个? 爱编程 今天 作者:mingo_敏 链接:https://blog.csdn. […]...

  4. 利用mapWithState实现按照首字母统计的有状态的wordCount

    最近在做sparkstreaming整合kafka的时候遇到了一个问题: 可以抽象成这样一个问题:有状态的wo […]...

  5. CSS基础:层叠顺序和层叠上下文

    简介   在考虑到两个元素可能重叠的情况下,层叠顺序决定了那个元素在前面,那个元素在后面,这是针对普通元素而言 […]...

  6. linux安装Linux下软件的安装与卸载方法

      在Windows下安装软件时,只需运行软件的安装程序(setup、install等)或者用zip等解压缩软 […]...

  7. 基于深度学习的人脸性别识别系统(含UI界面,Python代码)

    摘要:人脸性别识别是人脸识别领域的一个热门方向,本文详细介绍基于深度学习的人脸性别识别系统,在介绍算法原理的同时,给出Python的实现代码以及PyQt的UI界面。在界面中可以选择人脸图片、视频进行检测识别,也可通过电脑连接的摄像头设备进行...

  8. [altium] Altium Designer2013 13.3.4 (10.1881.28608) 完美版

    http://bbs.elecfans.com/jishu_386415_1_1.html...

展开目录

目录导航