目录

什么是装饰模式?

应用代码示例

装饰模式模板

jdk中的装饰模式

为什么装饰器类不能直接实现Component父类?


什么是装饰模式?

以生活中的场景来举例,一个蛋糕胚,给它涂上奶油就变成了奶油蛋糕,再加上巧克力和草莓,它就变成了巧克力草莓蛋糕。

像这样能够不断地向对象添加装饰的设计模式就称为装饰模式(Decorator模式)。

这里添加的东西就是装饰器,被添加的对象就是被装饰对象,在蛋糕的场景中,蛋糕是被装饰的对象,装饰器就是奶油,巧克力这些。一般来说装饰模式会满足is a的关系,被装饰对象如果怎么添加装饰,其本质不变,就像蛋糕加再多东西,其本质都是蛋糕。

应用代码示例

假设我们需要设计一个奖金的系统,目前一个工作人员的奖金包含三部分:

本月销售额的奖金 : 当月销售额的3%

累计销售额的奖金 : 累计销售额的0.1%

团队销售额的奖金 : 团队销售额的1%,只有经理才有

使用装饰模式的实现如下 : 定义一个所有奖金的抽象接口,然后定义一个BasicPrize的初始奖金对象,不同的人奖金的组成不同,就为其添加不同的装饰器

/*** @Description* @Author chenpp* @Date 2019/10/13 19:27*/
public abstract class Component {abstract double calPrize(String userName);
}
/*** @Description* @Author chenpp* @Date 2019/10/13 19:28* 默认的基础对象: 奖金--0*/
public class BasicPrize extends Component {//保存了一个员工与销售额的对应关系的mappublic static Map<String,Double> saleMoney = new HashMap<String,Double>();static {saleMoney.put("小明",9000.0);saleMoney.put("小陈",20000.0);saleMoney.put("小王",30000.0);saleMoney.put("张经理",55000.0);}public double calPrize(String userName) {return 0;}
}
/*** @Description* @Author chenpp* @Date 2019/10/13 19:28* 所有装饰器的抽象父类,持有一个被装饰对象*/
public abstract class Decorator extends Component {protected Component component;public Decorator(Component component){this.component = component;}public abstract double calPrize(String userName);
}
/*** @Description* @Author chenpp* @Date 2019/10/13 19:48* 当月奖金计算规则*/
public class MonthPrizeDecorator extends  Decorator{public MonthPrizeDecorator(Component component) {super(component);}public double calPrize(String userName)  {//先计算被装饰对象的奖金(就是在加上本奖金之前的奖金)double money = this.component.calPrize(userName);//计算本奖金  对应员工的业务额的3%double prize = BasicPrize.saleMoney.get(userName)*0.03;System.out.println(userName+"当月 业务奖金:"+prize);return money + prize;}}
/*** @Description* @Author chenpp* @Date 2019/10/13 19:33* 累计奖金*/
public class SumPrizeDecorator extends Decorator {public SumPrizeDecorator(Component component) {super(component);}public double calPrize(String userName)  {//先计算被装饰对象的奖金(就是在加上本奖金之前的奖金)double money = this.component.calPrize(userName);//计算本奖金  累计业务额的0.1% 假设是100000double prize = 100000*0.001;System.out.println(userName+"当月 累计奖金:"+prize);return money + prize;}}
/*** @Description* @Author chenpp* @Date 2019/10/13 19:34* 团队奖金*/
public class GroupPrizeDecorator extends Decorator {public GroupPrizeDecorator(Component component) {super(component);}public double calPrize(String userName)  {//先计算被装饰对象的奖金(就是在加上本奖金之前的奖金)double money = this.component.calPrize(userName);//计算本奖金  本团队的业务额的1%double sumSale = 0;for(double sale: BasicPrize.saleMoney.values()){sumSale += sale;}double prize = sumSale*0.01;System.out.println(userName+"当月 团队奖金:"+prize);return money + prize;}}
/*** @Description* @Author chenpp* @Date 2019/10/13 20:01*/
public class Client {public static void main(String[] args) throws FileNotFoundException {//基础对象 奖金0Component basePrice = new BasicPrize();//当月奖金Component salePrize = new MonthPrizeDecorator(basePrice);//累计奖金Component sumPrize = new SumPrizeDecorator(salePrize);//团队奖金Component groupPrize = new GroupPrizeDecorator(sumPrize);//普通员工的奖金由两部分组成double d1 = sumPrize.calPrize("小明");System.out.println("========================小明总奖金:"+d1);double d2 = sumPrize.calPrize("小陈");System.out.println("========================小陈总奖金:"+d2);double d3 = sumPrize.calPrize("小王");System.out.println("========================小王总奖金:"+d3);//王经理的奖金由三部分组成double d4 = groupPrize.calPrize("张经理");System.out.println("========================张经理总奖金:"+d4);}
}

输出结果:

这里我们使用装饰器模式,主要是为了方便组合和复用。在一个继承的体系中,子类往往是互斥的,比方在一个奶茶店,它会有丝袜奶茶,红茶,果茶等,用户想要一杯饮料,一般都会在这些种类中选一种,不能一杯饮料既是果茶又是奶茶。然后用户可以根据自己的喜好添加任何想要的decorators,珍珠,椰果,布丁等,这些添加物对所有茶类饮品都是相互兼容的,并且是可以被允许反复添加的(同样的装饰器是否允许在同一个对象上装饰多次,视情况而定,像上面的奖金场景显然是不被允许的)

装饰模式模板

public abstract class Component {abstract void operation();
}
/*** @Description* @Author chenpp* @Date 2019/10/13 19:28* 具体的被装饰类*/
public class ConcreteComponent extends   Component {public void operation() {}
}
/*** @Description* @Author chenpp* @Date 2019/10/13 19:28* 所有装饰器的抽象父类,持有一个被装饰对象*/
public class Decorator extends  Component {protected Component component;public Decorator(Component component){this.component = component;}public void operation() {component.operation();}
}

其角色可以分为三类:
Component : 组件对象的接口,可以给这些对象动态的添加功能
ConcreteComponent:具体的组件对象,实现了Component接口,通常就是被装饰器装饰的原始对象
Decorator:所有装饰器的抽象父类,需要定义与组件接口一致的接口,并且持有一个被装饰的Component对象

Decorator和ConcreteComponent的父类接口都是Component,所以能够保证在装饰后还拥有装饰前的特性,保证装饰器和被装饰物的一致性

其类图如下:

ConcreteDecoratorA和ConcreteDecoratorB是具体的装饰器对象,可以为原始对象添加不同的功能。理论上各装饰器之间的功能最好完全独立,互不依赖,这样用户在组合的时候才可以不受先后顺序的限制,更加灵活且安全。

jdk中的装饰模式

java.io包是用于输入输出的包,这里使用了大量的装饰器模式

以输出流为例,我们可以使用FileOutputStream fos = new FileOutputStream("file.txt");进行输出,也可以添加各种装饰器

eg.BufferedOutputStream,DataOutputStream 等

下图是jdk 输出流的一部分类图,很明显是一个装饰模式的类图,OutputStream是顶层父类,FileOutputStream和ObjectOutputStream是具体的被装饰类,FilterOutputStream是所有输出流装饰器的抽象父类

按照装饰器模式的结构,我们可以继承FilterOutputStream实现自定义的装饰器,并且在使用的时候可以和jdk自带的装饰器对象任意组合。

这里实现了一个简单的复制输出内容的OutputStream装饰器

public class DuplicateOutputStream2 extends FilterOutputStream {/*** Creates an output stream filter built on top of the specified* underlying output stream.** @param out the underlying output stream to be assigned to*            the field <tt>this.out</tt> for later use, or*            <code>null</code> if this instance is to be*            created without an underlying stream.*/public DuplicateOutputStream2(OutputStream out) {super(out);}//将所有的内容复制一份输出 ab 变成aabbpublic void write(int b) throws IOException {super.write(b);super.write(b);}
}

为什么装饰器类不能直接实现Component父类?

以输出流为例,如果直接继承OutputStream来实现自定义装饰器

public class DuplicateOutputStream extends OutputStream {private OutputStream os;public DuplicateOutputStream(OutputStream os){this.os = os;}//将所有的内容复制一份输出 ab 变成aabbpublic void write(int b) throws IOException {os.write(b);os.write(b);}
}
public class ClientTest {public static void main(String[] args) throws IOException {DataOutputStream dataOutputStream = new DataOutputStream(new BufferedOutputStream(new DuplicateOutputStream2(new FileOutputStream("1.txt"))));testOutputStream(dataOutputStream);DataOutputStream dataOutputStream1 = new DataOutputStream(new BufferedOutputStream(new DuplicateOutputStream(new FileOutputStream("1.txt"))));testOutputStream(dataOutputStream1);}public static void testOutputStream(DataOutputStream dataOutputStream) throws IOException {DataInputStream dataInputStream = new DataInputStream(new FileInputStream("1.txt"));dataOutputStream.write("bdsaq".getBytes());dataOutputStream.close();System.out.println(dataInputStream.available());byte[] bytes3 = new byte[dataInputStream.available()];dataInputStream.read(bytes3);System.out.println("文件内容:"+new String(bytes3));}
}

输出结果:

乍一看好像没什么区别,但是如果把BufferedOutputStream装饰器和自定义的装饰器互换。

 DataOutputStream dataOutputStream2 = new DataOutputStream(new DuplicateOutputStream2(new BufferedOutputStream(new FileOutputStream("1.txt"))));
testOutputStream(dataOutputStream2);DataOutputStream dataOutputStream3 = new DataOutputStream(new DuplicateOutputStream(new BufferedOutputStream(new FileOutputStream("1.txt"))));
testOutputStream(dataOutputStream3);

输出结果:

使用了实现OutputStream的DuplicateOutputStream会出现没有正常输出数据,这是因为我们使用了BufferedOutputStream这个带缓存区的输出流,缓存区的输出流在缓存区没有满的情形下是不会进行输出操作的。一般情形下我们在调用jdk的DataOutputStream的close方法的时候会调用其传入的输出流的flush()方法,并且向下传递调用,BufferedOutputStream里的数据会正常输出。

在使用DuplicateOutputStream2的时候其调用关系是这样的:

dataOutputStream.close()-->duplicateOutputStream2.flush()-->bufferedOutputStream.flush()

使用DuplicateOutputStream的时候由于DuplicateOutputStream继承的OutputStream的flush()方法是空实现,所以不会继续往下调用bufferedOutputStream的flush()方法,故而最后没有得到输出内容

所以装饰器类需要继承Decorator抽象父类,而不是直接继承Component抽象类,我认为是为了在Decorator里实现一些共性的代码,以便在使用装饰器的时候能够更加自由,无视其组合顺序


参考资料:

<<研磨设计模式>>

设计模式--装饰模式相关推荐

  1. 设计模式----装饰模式

    设计模式--装饰模式 "装饰模式(Decorator)"又名"包装模式(Wrapper)",通常用来灵活地扩充对象的功能. 在此之前我们可以通过类的继承来扩充父 ...

  2. 大话设计模式-装饰模式(大鸟和小菜Java版)

    装饰模式:装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能.它是通过创建一个包装对象,也就是装饰来包裹真实的对象.(百度百科) 这个模式让后期的修改变得极为简单,真的就高内 ...

  3. 大话设计模式—装饰模式

    装饰模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 这种模式创建了一个装饰类,用来包装原 ...

  4. C++设计模式-装饰模式

    目录 基本概念 代码和实例 基本概念 装饰模式是为已有功能动态地添加更多功能的一种方式. 当系统需要新功能的时候,是向旧系统的类中添加新代码.这些新代码通常装饰了原有类的核心职责或主要行为. 装饰模式 ...

  5. 李建忠设计模式——装饰模式

    1."单一职责"模式 在软件组件的设计中,如果责任划分不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任. 典型模式 Deco ...

  6. C++设计模式——装饰模式(高屋建瓴)

    原网址:https://blog.csdn.net/CoderAldrich/article/details/83115394 重点在于 ConcreteDecoratorA(Component *d ...

  7. java设计模式——装饰模式

    装饰模式也称为包装模式.结构型设计模式之一,其使用一种对客户端透明的方式动态的扩展对象的功能,同时它也是继承关系的一种替代方案之一. 装饰模式可以动态的给一个对象添加一些额外的职责.就增加功能功能来说 ...

  8. 设计模式——装饰模式详解

    0. 前言   写在最前面,本人的设计模式类博文,建议先看博文前半部分的理论介绍,再看后半部分的实例分析,最后再返回来复习一遍理论介绍,这时候你就会发现我在重点处标红的用心,对于帮助你理解设计模式有奇 ...

  9. 设计模式 | 装饰模式

    1 | 装饰模式的概述 我们在了解装饰模式之前,先回顾下生活中的几个常见现象,举例如下: 新房的装修,房屋装修并没有改变房屋居住的本质,但可以让房屋变得更漂亮,更温馨,更实用,更满足居家需求. 相片的 ...

最新文章

  1. pandas.apply 有源码github
  2. React从入门到精通系列之(1)安装React
  3. 《Neo4j全栈开发》_陈韶健
  4. mysql怎么查看索引情况_mysql 查看索引使用情况
  5. asp.net基础知识
  6. 专心做搜索也能登顶CLUE分类榜?在快手做搜索是一种怎样的体验
  7. python库路径_如何设置本地python库目录/ PYTHONPATH?
  8. 卢伟冰怼荣耀V30相机被喷 卢伟冰:从不打无准备之仗
  9. mybatis 多表关联查询_Java修行第041天--MyBatis框架(下)--多表查询
  10. java hashMap缓存简单实现
  11. C语言程序设计 第八章字符串
  12. 支持向量机原理与实现
  13. loadlibrary函数失败,错误码:126
  14. 【Cloudaily】3.15五招教你辨别真假云计算,2017 Gartner数据科学魔力象限出炉
  15. 第五人格显示服务器维护中请稍后登录怎么办,第五人格账号登录失败怎么办
  16. Harmonious Graph (并查集 —父亲为最大值)
  17. 删除一个字符串中指定位置上的字符
  18. 【疑难问题】——Game中子弹的代码结构设计(未完)——是每个实例去监听某个事件
  19. 2019年南京大学计算机系暨人工智能学院开放日和九月推免全记录
  20. linux下驱动编译报错EEROR: *** [***.ko] undefined! 的错误原因和解决办法

热门文章

  1. 代理模式 、JDK动态代理、cglib动态代理
  2. 验证用户输入的是不是中文名字 淘宝精品案例 元素样式设置的方式 链式编程
  3. JAVA程序中 + 号的使用
  4. python中如何删除字典中的元素_python中字典删除元素
  5. 第二十一章:变换(三)
  6. 【原】a.class与a .class的区别
  7. Centos7.1 命令行与图形化界面登陆
  8. 用Matlab与c++程序生成的数据文件绘制sin函数
  9. python的c语言扩展方法简介
  10. ipad4没有声音提示消息