装饰着模式
装饰着模式拥有一个设计非常巧妙的结构,它可以动态添加对象功能。在基础的设计原则中,有一条重要的设计准则叫合成/聚合复用原则。根据该原则的思想,代码复用应该尽可能使用委托,而不是使用继承。因为 继承是一种紧密耦合,任何父类的改动都会影响其子类,不利于系统维护。而委托则是松耦合,只要接口不变,委托类的改动并不会影响其上层对象。
装饰着模式就充分运用了这种思想,通过委托机制,复用系统中的各个组件,在运行时,可以将这些功能组件进行叠加,从而构建一个“超级对象”,使其拥有所有这些组件的功能。而各个子功能模块,被很好地维护在各个组件的相关类中,拥有整洁的系统结构。
装饰着模式这种结构可以很好地将功能最难喝性能组件进行分离,彼此互不影响,并在需要的时候,有机地结合起来。为了跟好地理解装饰着模式如何做到性能模块地分离,首先,需要对装饰着模式做一个总体对了解。
装饰着模式的基本机构如下图:
装饰者(Decorator)和被装饰者(ConcreteComponent)拥有相同的接口Component。
被装饰者通常是系统的核心组件,完成特定的功能目标。而装饰者则可以在被装饰者的方法前
后,加上特定的前置处理和后置处理,增强被装饰者的功能。
装饰者模式的主要角色如表:
角色 | 作用 |
组件接口 | 组件接口是装饰者和被装饰者的超类或者接口。它定义了被装饰者的核心功能和装饰者需要加强的功能点 |
具体组件 | 具体组件实现了组件接口的核心方法,完成某一个具体的业务逻辑。它也是被装饰的对象 |
装饰者 | 实现组件接口,并持有一个具体的被装饰对象 |
具体装饰者 | 具体实现装饰的业务逻辑,即实现了被分离的各个增强功能点。各个具体装饰者是可以互相叠加的,从而可以构成一个功能更强大的组件对象 |
装饰者模式的一个经典案例就是对输出结果进行增强。比如, 现在需要将一结果通过HTML进行发布,那么首先就需要将内容转化为一个HTML头。同时,由于内容需要在网络上通过HTTP流传,故,还需要为其增加HTTP头。当然作为一个更复杂的情况,可能还要为其安置TCP头等。但作为一个实例,这里做简化处理。
装饰者模式但核心思想在于:无需将所有的逻辑,即,核心内容构建、HTML文本构造和HTTP头生成等3个功能模块粘合在一起实现。通过装饰着模式,可以将他们分解为3个几乎完全独立的组件,并在使用时灵活地进行装配。为实现这个功能,可以使用如图所示结构:
IPacketCreator即装饰接口,用于处理具体的内容。PacketBodyCreator是具体的组件,它的功能是构造要发布信息的核心内容,但是它不负责将其构造成一个格式工整、可直接发布的数据格式。PacketHTTPHeaderCreator负责对给定的内容加上HTTP头部,PacketHTMLHeaderCreator负责将给定的内容格式化成HTML文本。如上图所示,3个功能模块相对独立且分离,易于系统维护。
IPacketCreator的实现很简单,他是一个但方法的接口:
public interface IPacketCreator { public String handleContent(); //用于内容处理 }
PacketBodyCreator 用于返回数据包的核心数据:
public class PacketBodyCreator implements IpacketCreator { @Override public String handleContent() { return "Content of Packet"; //构造核心数据,但不包括格式 } }
PacketDecorator维护核心组件component对象,它负责告知其子类,其核心业务逻辑应该全权委托component完成,
自己仅仅是做增强处理。
public abstract class PacketDecorator implements IPacketCreator { IPacketCreator component; public PacketDecorator(IPacketCreator c) { component = c; } }
PacketHTMLHeaderCreator是具体的装饰器,它负责对核心发布的内容进行HTML格式化操作。
需要特别注意的是,他委托了具体组件component进行核心业务处理。
public class PacketHTMLHeaderCreator extends PacketDecorator { public PacketHTMLHeaderCreator(IPacketCreator c) { super(c); } @Override public String handleContent() { StringBuffer sb = new StringBuffer(); sb.append("<html>"); sb.append("<body>"); sb.append(component.handleContent()); sb.append("</body>"); sb.append("</html>\n"); return sb.toString(); } }
PacketHTTPHeaderCreator 与 PacketHTMLHeaderCreator类似,但是它完成数据包HTTP头部的处理。
其余业务处理依然交由内部的component完成。
public class PacketHTTPHeaderCreator extends PacketDecorator { public PacketHTTPHeaderCreator(IPacketCreator c) { super(c); } @Override public String handleContent() { //对给定数据加上HTTP头信息 StringBuffer sb = new StringBuffer(); sb.append("Cache-control:no-cache\n"); sb.append("Date:Mon,31Dec201204:25:57GMT\n"); sb.append(component.handleContent()); return sb.toString(); } }
对于装饰者模式,另一个值得关注的地方是它的使用方法。在本例中,通过层层构造和组装这些装饰者和被装饰者到一个对象中,
使其有机地结合在一起工作。
public class Main { public static void main(String[] args) { IPacketCreator pc = new PacketHTTPHeaderCreator( new PacketHTMLHeaderCreator( new PacketBodyCreator())); System.out.println(pc.handleContent()); } }
可以看到,通过装饰者的构造函数,将被装饰对象传入。本例中,共生成3个对象实例,作为核心组件的PacketBodyCreator ,
最先被构造,其次是PacketHTMLHeaderCreator,最后才是PacketHTTPHeaderCreator。
这个顺序表示,首先由PacketBodyCreator对象去生成核心发布内容,接着由PacketHTMLHeaderCreator对象对这个内容进行处理,将其转化为HTML,最后由PacketHTTPHeaderCreator对PacketHTMLHeaderCreator的输出安装HTTP头部。程序运行结果如下:
下图是本例的调用堆栈,从调用堆栈中,应该可以更容易地理解各个组件的相互关系。
在JDK的实现中,有不少组件也是用装饰着模式实现的。其中,一个最典型的例子就是OutputStream和InputStream类族的实现。以OutputStream为例,OutputStream对象提供的方法比较简单,功能也比较挼2,但通过各种装饰者但增强,OutputStream对象可以被赋予强大的功能。
下图显示以OutputStream为核心的装饰者模式的实现。其中FileOutputStream为系统的核心类,它实现了向文件写入数据。使用DataOutputStream可以在FileOutputStream的基础上,增加对多种数据类型的写操作支持,而BufferedOutputStream装饰器,可以对FileOutputStream增加缓冲功能,优化I/O的性能。以BufferedOutputStream为代表的性能组件,是将性能模块和功能模块分离的一种典型实现。
public static void main(String[] args) throws IOException { //生成一个有缓冲功能的流对象 //spend:21 DataOutputStream dout = new DataOutputStream( new BufferedOutputStream( new FileOutputStream("C:\\a.txt"))); //没有缓冲功能的流对象 //spend:592 DataOutputStream dout1 = new DataOutputStream( new FileOutputStream("C:\\a.txt")); long begin = System.currentTimeMillis(); for (int i =0;i<100000;i++){ dout.write(i); //dout1.write(i); } System.out.println("spend:" + (System.currentTimeMillis() - begin)); }
以上代码显示FileOutputStream的典型应用。加粗部分是两种建立OutputStream的方法,第一种假如了性能组件BufferedOutputStream,第二种则没有。因此,第一种方法产生的OutputStream拥有更好的I/O性能。
下面看一下装饰者模式如何通过性能组件增强I/O性能。在运行时,工作流程如图:
在FileOutputStream.write()的调用之前,会首先调用BufferedOutputStream.write(),它的实现如下:
可以看到,并不是每次BufferedOutputStream.write()调用都会去磁盘写入数据,而是将数据写入缓存中,当缓冲写满时,才调用FileOutputStream.write()方法,实际写入数据。以此实现性能组件与功能组件的完美分离。
原文地址:https://www.cnblogs.com/klyjb/p/11527591.html
- 如何在CM中启用YARN的使用率报告
- 如何修改CDSW服务的DNS和HOSTNAME
- 想尝试搭建图像识别系统?这里有一份TensorFlow速成教程
- 如何查看集成Sentry后Hive作业的真实用户
- 如何在Impala中实现拉链表
- 多图见证模拟机器人的逆天成长:论进化策略在强化学习中的应用
- Hue禁止用户下载数据问题分析
- 如何在RedHat6上使用Bind搭建DNS服务
- 如何使用Cloudera Manager升级Spark2.1版本至Spark2.2
- Cloudera Manager分发Parcel异常分析
- 如何在Hue中配置Impala的负载均衡
- 35行代码实现千万级别字典的快速去重
- PIMS三个漏洞+里程密最新版V2.3 SQL注入漏洞
- 如何在Redhat7.3安装CDH5.14
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法