Java设计模式12:装饰器模式

装饰器模式

装饰器模式又称为包装(Wrapper)模式。装饰器模式以多客户端透明的方式扩展对象的功能,是继承关系的一个替代方案

 

装饰器模式的结构

通常给对象添加功能,要么直接修改对象添加相应的功能,要么派生子类来扩展,抑或是使用对象组合的方式。显然,直接修改对应的类的方式并不可取,在面向对象的设计中,我们应该尽量使用组合对象而不是继承对象来扩展和复用功能,装饰器模式就是基于对象组合的方式的。

装饰器模式以对客户端透明的方式动态地给一个对象附加上了更多的责任。换言之,客户端并不会角色对象在装饰前和装饰后有什么不同。装饰器模式可以在不用创建更多子类的情况下,将对象的功能加以扩展。

装饰器模式中的角色有:

1、抽象构件角色

给出一个抽象接口,以规范准备接受附加责任的对象

2、具体构件角色

定义一个将要接受附加责任的类

3、装饰角色

持有一个构建对象的实例,并定义一个与抽象构件接口一致的接口

4、具体装饰角色

负责给构建对象贴上附加的责任

 

装饰器模式的例子

现在有这么一个场景:

1、有一批厨师,简单点吧,就全是中国厨师,他们有一个共同的动作是做晚饭

2、这批厨师做晚饭前的习惯不同,有些人喜欢做晚饭前洗手、有些人喜欢做晚饭前洗头

那么,按照装饰器模式,先抽象出抽象构建角色,Cook接口:

public interface Cook {public void cookDinner();}

具体构建角色,中国厨师:

public class ChineseCook implements Cook {@Overridepublic void cookDinner() {System.out.println("中国人做晚饭");}}

定义一个装饰器角色,具体的工作具体装饰器去实现,这样,比如美国厨师做晚饭前也先洗手或者先洗头,这两个动作就可以做到复用,装饰器角色定义为FilterCook,很简单,实现Cook接口并持有Cook的引用:

public abstract class FilterCook implements Cook {protected Cook cook;}

最后定义一个具体装饰角色,该洗手的洗手,该洗头的洗头:

public class WashHandsCook extends FilterCook {public WashHandsCook(Cook cook) {this.cook = cook;}@Overridepublic void cookDinner() {System.out.println("先洗手");cook.cookDinner();}}
public class WashHearCook extends FilterCook {public WashHearCook(Cook cook) {this.cook = cook;}@Overridepublic void cookDinner() {System.out.println("先洗头");cook.cookDinner();}}

调用方这么实现:

@Test
public void testDecorate() {Cook cook0 = new WashHandsCook(new ChineseCook());Cook cook1 = new WashHearCook(new ChineseCook());cook0.cookDinner();cook1.cookDinner();
}

运行结果为:

先洗手
中国人做饭
先洗头
中国人做饭

简单的一个例子,实现了装饰器模式的两个功能点:

  1. 客户端只定义了Cook接口,并不关心具体实现
  2. 给Chinese增加上了洗头和洗手的动作,且洗头和洗手的动作,可以给其他国家的厨师类复用

这就是装饰器模式。

 

装饰器模式与Java字节输入流InputStream

上面的例子可能写得不是很清楚,因此这里再继续用代码示例讲解装饰器模式。

装饰器模式在Java体系中的经典应用是Java I/O,下面先讲解字节输入流InputStream,再讲解字符输入流Reader,希望可以通过这两种输入流的讲解,加深对于装饰器模式的理解。

首先看一下字节输入流InputStream的类结构体系:

InputStream是一个顶层的接口,文章开头就说,装饰器模式是继承关系的一种替代方案,看一下为什么:

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

这样就导致两个问题:

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

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

  1. 在要扩展的类比如BufferedInputStream中持有一个InputStream的引用,在BufferedInputStream调用InputStream中的方法,这样扩展的代码就可以复用起来
  2. 将BufferedInputStream作为InputStream的子类,这样客户端只知道我用的是InputStream而不需要关心具体实现,可以在客户端不知情的情况下,扩展InputStream的功能,加上缓冲功能

这就是装饰器模式简单的由来,一切都是为了解决实际问题而诞生。下一步,根据UML图,我们来划分一下装饰器模式的角色。

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

public abstract class InputStream implements Closeable {// SKIP_BUFFER_SIZE is used to determine the size of skipBufferprivate static final int SKIP_BUFFER_SIZE = 2048;// skipBuffer is initialized in skip(long), if needed.private static byte[] skipBuffer;...
}

2、ByteArrayInputStream、FileInputStream、ObjectInputStream、PipedInputStream都是具体构建角色,比如FileInputStream,它的声明是:

public
class FileInputStream extends InputStream
{/* File Descriptor - handle to the open file */private FileDescriptor fd;private FileChannel channel = null;...
}

3、FilterInputStream无疑就是一个装饰角色,因为FilterInputStream实现了InputStream内的所有抽象方法并且持有一个InputStream的引用:

public
class FilterInputStream extends InputStream {/*** The input stream to be filtered. */protected volatile InputStream in;...
}

4、具体装饰角色就是InflaterInputStream、BufferedInputStream、DataInputStream,比如BufferedInputStream的声明就是:

public
class BufferedInputStream extends FilterInputStream {private static int defaultBufferSize = 8192;/*** The internal buffer array where the data is stored. When necessary,* it may be replaced by another array of* a different size.*/protected volatile byte buf[];...
}

搞清楚具体角色之后,我们就可以这么写了:

public static void main(String[] args) throws Exception
{File file = new File("D:/aaa.txt");InputStream in0 = new FileInputStream(file);InputStream in1 = new BufferedInputStream(new FileInputStream(file)); InputStream in2 = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
}

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

  1. in0这个引用指向的是new出来的FileInputStream,这里简单构造出了一个文件字节输入流
  2. in1这个引用指向的是new出来的BufferedInputStream,它给FileInputStream增加了缓冲功能,使得FileInputStream读取文件的内容保存在内存中,以提高读取的功能
  3. in2这个引用指向的是new出来的DataInputStream,它也给FileInputStream增加了功能,因为它有DataInputStream和BufferedInputStream两个附加的功能

同理,我要给ByteArrayInputStream、ObjectInputStream增加功能,也可以使用类似的方法,整个过程中,最重要的是要理解几个问题:

  1. 哪些是具体构建角色、哪些是具体装饰角色,尤其是后者,区分的关键就是,角色中是否持有顶层接口的引用
  2. 每个具体装饰角色有什么作用,因为只有知道每个具体装饰角色有什么作用后,才可以知道要装饰某个功能需要用哪个具体装饰角色
  3. 使用构造方法的方式将类进行组合,给具体构建角色加入新的功能

 

装饰器模式与Java字符输入流Reader

看完了上面的解读,相信大家对于装饰器模式应当有了一定的理解,那么再来看一下Java字符输入流Reader,来加深对于装饰器模式的印象。

简单看一下Reader的类体系结构:

根据UML,分析一下每个角色:

1、抽象构建角色

毫无疑问,由Reader来扮演,它是一个抽象类,没有具体功能

2、具体构建角色

由InputStreamReader、CharArrayReader、PipedReader、StringReader来扮演

3、装饰角色

由FilterReader来扮演,但是这里要提一下这个BufferedReader,它本身也可以作为装饰角色出现,看一下BufferedReader的继承关系:

public class BufferedReader extends Reader {private Reader in;private char cb[];private int nChars, nextChar;private static final int INVALIDATED = -2;private static final int UNMARKED = -1;private int markedChar = UNMARKED;...
}

看到BufferedReader是Reader的子类,且持有Reader的引用,因此这里的BufferedReader是可以被认为是一个装饰角色的。

4、具体装饰角色

BufferedReader上面提到了扮演了装饰角色,但是也可以被认为是一个具体装饰角色。除了BufferedReader,具体装饰角色还有PushbackReader。FileReader尽管也在第三行,但是FileReader构不成一个具体装饰角色,因为它不是BufferedReader的子类也不是FilterReader的子类,不持有Reader的引用

 

半透明装饰器模式与全透明装饰器模式

再说一下半透明装饰器模式与全透明装饰器模式,它们的区别是:

  1. 对于半透明装饰器模式,装饰后的类未必有和抽象构件角色同样的接口方法,它可以有自己扩展的方法
  2. 对于全透明装饰器模式,装饰后的类有着和抽象构件角色同样的接口方法

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

比如BufferedInputStream吧,我把FileInputStream装饰为BufferedInputStream,难道BufferedInputStream就完全没有自己的行为?比如返回缓冲区的大小、清空缓冲区(这里只是举个例子,实际BufferedInputStream是没有这两个动作的),这些都是InputStream本身不具备的,因为InputStream根本不知道缓冲区这个概念,它只知道定义读数据相关方法。

所以,更多的我们是采用半透明的装饰器模式,即允许装饰后的类中有属于自己的方法,因此,前面的I/O代码示例可以这么改动:

public static void main(String[] args) throws Exception
{File file = new File("D:/aaa.txt");FileInputStream in0 = new FileInputStream(file);BufferedInputStream in1 = new BufferedInputStream(new FileInputStream(file)); DataInputStream in2 = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
}

这样才更有现实意义。

 

装饰器模式的优缺点

优点

1、装饰器模式与继承关系的目的都是要扩展对象的功能,但是装饰器模式可以提供比继承更多的灵活性。装饰器模式允许系统动态决定贴上一个需要的装饰,或者除掉一个不需要的装饰。继承关系是不同,继承关系是静态的,它在系统运行前就决定了

2、通过使用不同的具体装饰器以及这些装饰类的排列组合,设计师可以创造出很多不同的行为组合

缺点

由于使用装饰器模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是另一方面,由于使用装饰器模式会产生比使用继承关系更多的对象,更多的对象会使得查错变得困难,特别是这些对象看上去都很像。

 

装饰器模式和适配器模式的区别

其实适配器模式也是一种包装(Wrapper)模式,它们看似都是起到包装一个类或对象的作用,但是它们使用的目的非常不一样:

1、适配器模式的意义是要将一个接口转变成另外一个接口,它的目的是通过改变接口来达到重复使用的目的

2、装饰器模式不要改变被装饰对象的接口,而是恰恰要保持原有的借口哦,但是增强原有接口的功能,或者改变元有对象的处理方法而提升性能

所以这两种设计模式的目的是不同的。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/262591.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

差分管电路图_最简单的单差分OCL功放电路图(四款单差分OCL功放电路设计原理图详解)...

最简单的单差分OCL功放电路图(一)本电路采用了单管输入、推挽放大和阻容耦舍的输入级,解决了差分管配对的难题,而实际电路又达到了较高的技术水平,是一款十分优秀的功放电路。电路原理如图2-8所示。它由三部分组成:输入级、中间缓…

Google Maps API 代码

阅读全文并下载&#xff1a;http://www.cckan.net/forum.php?modviewthread&tid54 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns"http:/…

JDBC使用案例

一、结果集中获取数据并对实体set值&#xff0c;封装成对象返回&#xff1b; 2、封装JDBC工具类 只要执行一次的&#xff0c;如读取配置文件&#xff0c;则写静态代码块&#xff1b; 异常抛出要写明方法才可以throw&#xff0c;静态代码块异常只能捕捉try catch; 类加载器两个作…

Android入门:Log介绍

一、Log类介绍 在Logcat视图中查看Android日志信息&#xff1b; (1)Log.v()&#xff1a;任意信息&#xff1b; (2)Log.e()&#xff1a;error信息&#xff1b; (3)Log.w()&#xff1a;warning信息&#xff1b; (4)Log.i()&#xff1a;提示信息&#xff1b; (5)Log.d()&#xff1…

docker启动nginx后挂了_Docker容器部署 Nginx服务

1.查找 Docker Hub 上的 nginx 镜像[rootlocalhost ~]# docker search nginx2.拉取官方的Nginx镜像[rootlocalhost ~]# docker pull nginx3.在本地镜像列表里查到 REPOSITORY 为 nginx 的镜像[rootlocalhost ~]# docker images nginxREPOSITORY TAG IMAGE ID CREATED SIZEdocke…

JAVA中的日志框架-log4j的使用

JAVA日志-使用log4j 1. log4j.jar下载 windows下载地址&#xff1a; http://www.apache.org/dyn/closer.cgi/logging/log4j/1.2.15/apache-log4j-1.2.15.zip Linux平台下的下载地址&#xff1a; http://download.chinaunix.net/download.php?id12696&ResourceID6256 把l…

childNodes详解

定义和用法 childNodes 属性返回节点的子节点集合&#xff0c;以 NodeList 对象。 提示&#xff1a;您可以使用 length 属性来确定子节点的数量&#xff0c;然后您就能够遍历所有的子节点并提取您需要的信息。 浏览器支持 所有主流浏览器都支持 childNodes 属性。 语法 element…

PreparedStatement预编译的sql执行对象

一、预编译&#xff0c;防sql注入 其中&#xff0c;设置参数值占位符索引从1开始&#xff1b;在由sql 连接对象创建 sql执行对象时候传入参数sql语句&#xff0c;在执行对象在执行方法时候就不用再传入sql语句&#xff1b; 数据库索引一般是从1开始&#xff0c;java对象一般是从…

祝贺父亲节快乐的python代码_祝福父亲节快乐的句子50句

祝福父亲节快乐的句子50句祝福父亲节快乐的句子50句导语&#xff1a;他是一棵苍天大树&#xff0c;为我们遮风挡雨&#xff0c;呵护我们健康成长。下面励志故事网小编为大家整理了祝福父亲节快乐的句子&#xff0c;希望大家喜欢。1. 老爸老爸您最棒&#xff0c;身强体壮有力量。…

Linux下Vi/Vim的使用方法

本文介绍了vi (vim)的基本使用方法&#xff0c;但对于普通用户来说基本上够了&#xff01;i/vim的区别简单点来说&#xff0c;它们都是多模式编辑器&#xff0c;不同的是vim 是vi的升级版本&#xff0c;它不仅兼容vi的所有指令&#xff0c;而且还有一些新的特性在里面。例如语法…

JDBC管理事务

一、事务概念&#xff1a;打包一起的多个步骤的业务操作&#xff0c;要么同事成功&#xff0c;要么同时失败&#xff0c;则需要用事务管理&#xff1b; 二、代码实现 转载于:https://www.cnblogs.com/wmqiang/p/11600776.html

C++标准库简介

C标准库的所有头文件都没有扩展名。C标准库的内容总共在50个标准头文件中定义&#xff0c;其中18个提供了C库的功能。 <cname>形式的标准头文件【 <complex>例外】其内容与ISO标准C包含的name.h头文件相同&#xff0c;但容纳了C扩展的功能。在 <cname>形式标…

Android数据的四种存储方式SharedPreferences、SQLite、Content Provider和File (二) —— SQLite...

SQLite是一种转为嵌入式设备设计的轻型数据库&#xff0c;其只有五种数据类型&#xff0c;分别是&#xff1a; NULL&#xff1a; 空值 INTEGER&#xff1a; 整数 REAL&#xff1a; 浮点数 TEXT&#xff1a; 字符串 BLOB&#xff1a; 大数据 在SQLite中&#xff0c;并没有专门设…

netflix会员和非会员的区别_Netflix开放10部作品免费给非会员试看

DoNews 9月1日消息(记者 刘文轩)据TechCrunch消息&#xff0c;视频流媒体巨头Netflix向全球200多个国家的非付费用户免费开放10部影视作品&#xff0c;包括Netflix原创电影和电视节目。这些节目包括&#xff1a;《蒙上你的眼》(Bird Box)《宝贝老板&#xff1a;重围商界》(The …

根据企业信息化应用需求来分析工作流平台的选型

随着企业信息化建设的发展&#xff0c;选择一个能快速满足个性化需求并灵活稳健的系统架构是企业信息化建设成功的基础&#xff0c;这已经成为一个不争的事实。工作流平台软件作为一个中间件产品&#xff0c;在企业信息系统架构中对于业务流程管理、快速开发及高效维护起着相当…

excel 某个单元格不是等于空值_这些稀奇古怪的符号,却是Excel高手们常玩的!...

Excel高手的世界&#xff0c;刚接触excel的同学们&#xff0c;真还不懂。高手们在编写excel函数公式&#xff0c;经常会玩大括号({})、双减号(--)、百分号(%)、连接空值(&"")等等稀奇古怪的符号&#xff0c;看得我们是一愣愣的。当然这些进阶的思路也是excel帮助…

C语言漫谈

C语言是被使用的最广泛的一种高级语言&#xff0c;其历史相当久远。而其发展也相当神速&#xff0c; 从当初的标准C发展到后来的C。其性能也发生了很多很大的变化。C语言拥有众多的编译器&#xff0c;其中不乏优秀者众多。从当初的Turbo C引入集成化编译环境后&#xff0c;C语言…

使用git如何批量对文件进行rm操作

git add -A 它会把我们未通过 git rm 删除的文件全部stage 转自&#xff1a; http://segmentfault.com/q/1010000000095373

Cookie会话技术

java web动态资源有Servlet和JSP&#xff1b; HTTP协议是无状态的&#xff0c;即每次请求响应和其他的互补相关&#xff0c;所以要共享数据&#xff0c;需要会话技术&#xff1b; 一、概念 二、Cookie会话技术 代码写在服务器端&#xff0c;设置Cookie和发送Cookie以及接收Cook…

黑马程序员——生成html静态页面,方便seo,加快加载速度

---------------------- Windows Phone 7手机开发、.Net培训、期待与您交流&#xff01; ---------------------- 这几日&#xff0c;同学问我怎样将动态页面直接生车静态的html页面&#xff0c;我想了想以前做过这个&#xff0c;今天把方法做个总结。 我所知道的生成静态页面的…