装饰器模式

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

装饰器模式的结构

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

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

装饰器模式中的角色有:

1、抽象构件角色

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

2、具体构件角色

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

3、装饰角色

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

4、具体装饰角色

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

装饰器模式的例子

现在有这么一个场景:

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

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

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

public interface Cook {

    public void cookDinner();

}

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

public class ChineseCook implements Cook {

    @Override
public 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;
} @Override
public void cookDinner() {
System.out.println("先洗手");
cook.cookDinner();
} }
public class WashHearCook extends FilterCook {

    public WashHearCook(Cook cook) {
this.cook = cook;
} @Override
public 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 skipBuffer
private 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、装饰器模式不要改变被装饰对象的接口,而是恰恰要保持原有的借口哦,但是增强原有接口的功能,或者改变元有对象的处理方法而提升性能

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

Java设计模式12:装饰器模式的更多相关文章

  1. Java设计模式系列-装饰器模式

    原创文章,转载请标注出处:<Java设计模式系列-装饰器模式> 一.概述 装饰器模式作用是针对目标方法进行增强,提供新的功能或者额外的功能. 不同于适配器模式和桥接模式,装饰器模式涉及的是 ...

  2. java设计模式之七装饰器模式(Decorator)

    顾名思义,装饰模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例,关系图如下: Source类是被装饰类,Decorator类是一个 ...

  3. java设计模式之 装饰器模式

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

  4. Java设计模式之装饰器模式

    1.装饰器模式的定义(保持接口,扩展功能) Decorate装饰器,顾名思义,就是动态的给一个对象添加一些额外的职责,就好比对房子进行装修一样. 2.装饰器模式的特征 具有一个装饰对象. 必须拥有与被 ...

  5. java设计模式之装饰器模式以及在java中作用

    在JAVA I/O类库里有很多不同的功能组合情况,这些不同的功能组合都是使用装饰器模式实现的,下面以FilterInputStream为例介绍装饰器模式的使用  FilterInputStream和F ...

  6. java 设计模式 之 装饰器模式

    装饰器模式的作用 在不修改原先对象核心的功能的情况下,对功能进行增强. 增强对象的功能的途径 通过类继承的方式,对父对象进行增强操作,例如造车是父类,改装跑车,跑车加大灯,改装房车,房车加私人电影院. ...

  7. Java 设计模式泛谈&装饰者模式和单例模式

    设计模式(Design Pattern) 1.是一套被反复使用.多人知晓的,经过分类编目 的 代码设计经验总结.使用设计模式是为了可重用代码,让代码更容易维护以及扩展. 2.简单的讲:所谓模式就是得到 ...

  8. Java设计模式 - - 单例模式 装饰者模式

    Java设计模式 单例模式 装饰者模式 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 静态代理模式:https://www.cnblogs.com/StanleyBlogs/p/1 ...

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

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

  10. PHP设计模式之装饰器模式(Decorator)

    PHP设计模式之装饰器模式(Decorator) 装饰器模式 装饰器模式允许我们给一个类添加新的功能,而不改变其原有的结构.这种类型的类属于结构类,它是作为现有的类的一个包装 装饰器模式的应用场景 当 ...

随机推荐

  1. 关于python的requests库抓取源文件中文乱码的情况

    import requests html=requests.get('http://www.12306.cn/mormhweb/') print html.text 刚开始是这么写的,运行时一直弹出错 ...

  2. DEV设计之自动流水号,DEV专家解答,自己折腾了半天也没有搞定,怪英文不好

    () 老外专家给了回答,结果没有全到懂,又折腾了20分钟朋友提示才搞定 获取一个自动增加1的流水号值, 第一个参数是本事的数据库连接对象,第2个参数是也这个值为唯一标识返回来一个增量的值,第三个好像没 ...

  3. linux开机启动程序

    一./etc/rc.local这是一个最简单的方法,编辑“/etc/rc.local”,把启动程序的shell命令输入进去即可(要输入命令的全路径),类似于windows下的“启动”. 使用命令 vi ...

  4. Maven入门示例(3):自动部署至外部Tomcat

    Maven入门示例(3):自动部署至外部Tomcat 博客分类:  maven 2012原创   Maven入门示例(3):自动部署至外部Tomcat 上一篇,介绍了如何创建Maven项目以及如何在内 ...

  5. 深入研究C语言 第三篇

    本篇研究TC2.0下其他几个工具.同时看看TC由源代码到exe程序的过程. 1. 用TCC将下面的程序编为.obj文件 我们知道,TCC在默认的编译连接一个C语言的源程序a.c的时候分为以下两步: ( ...

  6. hdoj 1002 A+B(2)

    Problem Description I have a very simple problem for you. Given two integers A and B, your job is to ...

  7. C# 基础(7)--线程

    进程与线程 一个程序就是一个进程,进程包含线程,多个线程之间可以“并发”执行(确切的说是不停地切换),CPU是跟线程打交道的. 线程是程序的一个执行流,每个线程都有之间专有的寄存器(栈指针.程序计数器 ...

  8. cherry Tree的严重漏洞

    Cherry Tree程序员笔记软件存在严重漏洞 如果你用Cherry Tree来保存自己觉得很重要的东西的时候,你应该注意了 显示漏洞的步骤:(不用使用自己使用Cherry Tree保存的文件来做下 ...

  9. 关于mha手动切换的一些记录(mha方案来自网络)

    mha方案出自:http://www.cnblogs.com/xuanzhi201111/p/4231412.html 当主服务器故障时,人工手动调用MHA来进行故障切换操作,具体命令如下: 先停MH ...

  10. Android数据存储之SharedPreferences存储

    安卓系统为应用提供了系统级的配置存储方案,它就是靠SharedPreferences接口来实现的,该接口存储的所有信息都是以名值对的形式保存,但其保存的数据类型也仅限于基本数据类型,如字符串.整形.布 ...