18、装饰器模式(Decorator Pattern)

1. 装饰器模式

1.1 简介

  Decorator模式就是在不改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。这些功能需要由用户动态决定加入的方式和时机,Decorator提供了"即插即用"的方法,在运行期间决定何时增加何种功能。

  通过创建一个包装对象(装饰),来包裹真实的对象。软件开发的某个阶段和装修房子像极了!系统的基本功能实现后,需要完善功能和修饰界面,但基本功能和流程框架不会做大的改动。房屋的装修,是在毛胚房的基础上层层 wrapper(包装),先刷刷墙面漆,再铺铺木地板,再购置家具布置一下等,可以看作是对毛胚房层层包装。所以Decorator 设计模式也被称为 Wrapper 设计模式。

1.2

  Decorator 设计模式正如毛胚房的装修,不会改变原毛胚房的基本框架,只是增加新的外观、功能等,且随着时间的推移,可以不断的实施装修工程:增加新的家具、根据心情换换新鲜的墙纸等等。在面向对象的程序设计中,扩展系统的原有功能也可以采用继承、组合的方式。继承也不会改变毛胚房(父类),但是由于装修工程的复杂和很多不可预测的改变,比如不同墙纸和地板样式的组合数量简直无法想想,难道我们要为每一种组合都定义一个子类吗?显然这是不现实的,即通过继承的方式来应对未来的功能和外观改变通常是吃力不讨好的事情。组合的方式也不可取,因为这要求不断的修改父类的结构,相当于对毛胚房大动干戈,房屋的可维护性和可靠性就大大降低了。

  让我们回顾一下设计模式的重要原则:Classes should be open for extenstion, but closed for modification。Decorator 设计模式很好的诠释了这个原则。

1.3 Decorator模式结构

Decorator模式uml:

Decorator模式uml.png

Decorator模式角色:

  • Component为统一接口,也是装饰类和被装饰类的基本类型。
  • ConcreteComponent为具体实现类,也是被装饰类,他本身是个具有一些功能的完整的类。
  • Decorator是装饰类,实现了Component接口的同时还在内部维护了一个ConcreteComponent的实例,并可以通过构造函数初始化。
  • ConcreteDecorator是具体的装饰产品类,每一种装饰产品都具有特定的装饰效果。

2. Decorator模式示例

  我们以毛胚房的装修为例,我们可以在毛坯房的基础上刷墙漆、铺地板、安装电器等等。

Component接口:

public interface Room {
 public String showRoom();
}

毛坯房 ConcreteComponent:

public class BlankRoom implements Room {
 
    @Override
 public String showRoom() {
 return "毛坯房";
 }
}

虚拟装修器RoomDecorator:

// 装修工程的模板
abstract public class RoomDecorator implements Room {
 // wrapper 的具体体现,每个独立的装修工序都是在上一个装修工序
 // 的基础上进行的,装修就是这样层层包装完成的
 protected Room roomToBeDecorated;
 
 public RoomDecorator(Room roomToBeDecorated) {
 this.roomToBeDecorated = roomToBeDecorated;
 }
 
 @Override
 public String showRoom() {
 // 委托(delegate)
 return roomToBeDecorated.showRoom();
 }
}

PaintedDecorator:

 public PaintedDecorator(Room roomToBeDecorated) {
 super(roomToBeDecorated);
 }
 
 public String showRoom(){
 doPainting();
 return super.showRoom() + "刷墙漆"; 
 }
 
 // 刷墙漆
 private void doPainting(){}
 
}

FlooredDecorator:

public class FlooredDecorator extends RoomDecorator {
 public FlooredDecorator(Room roomToBeDecorated) {
 super(roomToBeDecorated);
 }
 
 
 public String showRoom(){
 doFlooring();
 return super.showRoom() + "铺地板";
 }
 
 // 铺地板
 private void doFlooring(){}
}

调用示例:

 public static void main(String[] args) {
 // 毛胚房
 Room blankRoom = new BlankRoom(); 
 
 // 刷了墙的毛胚房
 Room paintedRoom = new PaintedDecorator(new BlankRoom()); 
 
 // 先刷墙再铺地板的毛胚房
 // 注意到连续的 new 操作,这就是 wrapper,最内层的一般是毛胚房
 Room paintedAndFlooredRoom = new FlooredDecorator(new 
PaintedDecorator(new BlankRoom()));
 
 // 先铺地板再刷墙的毛胚房
 Room flooredAndPaintedRoom = new PaintedDecorator(new 
FlooredDecorator(new BlankRoom()));
 
 System.out.println(blankRoom.showRoom());
 System.out.println(paintedRoom.showRoom());
 System.out.println(paintedAndFlooredRoom.showRoom());
 System.out.println(flooredAndPaintedRoom.showRoom());
 }

Decorator模式优点:

  • 装饰器模式和继承的共同特点就是扩展对象的功能,而装饰器模式比静态继承更加灵活.
  • 通过使用不同的具体装饰器类,及其不同的排列组合,可以产生出大量不同的组合.
  • 避免在层次结构高层的类有太多的特征
  • Decorator与其他的Component不一样,Decorator说一个透明的包装。

Decorator模式缺点:

  • 会出现一些小类(小程序),过度使用会使程序变得复杂.

3. CDI 对 Decorator模式的支持

  Decorator 设计模式虽然降低了需求变更对软件开发的影响,但是通过层层包装,即层层 new 操作创建对象的方式不够优雅。CDI 容器可以管理组件的生命周期,在大部分情况下我们无须通过 new 操作创建所需要的对象。CDI 中的 Decorator/Delegate 注解很大程度上简化了 Decorator 设计模式的代码编写量,比如实现上面相同的功能,借助于 CDI,就无须 RoomDecorator 这个抽象类了,所有的 Decorator 类直接实现 Room 接口并使用注解声明为 Decorator 即可,比如 PaintedDecorator 类:

** PaintedDecorator:**

import javax.decorator.Decorator;
import javax.decorator.Delegate;
import javax.inject.Inject;
 
@Decorator
public class PaintedDecorator implements Room {
 
 @Inject
 @Delegate
 Room roomToBeDecorated;
 
 public String showRoom(){
 doPainting();
 return roomToBeDecorated.showRoom() + "刷墙漆"; 
 }
 
 // 刷墙漆
 private void doPainting(){}
}

** RoomController 中是这样使用 Decorator 类的:**

import javax.inject.Inject;
import javax.inject.Named;
 
@Named("room")
public class RoomController {
 
 @Inject
 Room room;
 
 public String showRoom(){
 return room.showRoom();
 }
}

  一切看起来简单多了,秘密就在于 CDI 的 beans.xml 文件,CDI 会根据 beans.xml 文件中对 Decorator 的声明顺序加载并构造相应的 Decorator 对象,完全复现了传统方式的 Decorator 模式中通过 new 操作层层包装“毛胚房”的过程。

beans.xml:

<?xml version="1.0" encoding="UTF-8"?>
 
<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
 http://java.sun.com/xml/ns/javaee
 http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
 
 <!-- To activate CDI decorator, it must be specified below -->
 <decorators>
 <class>cn.edu.sdut.r314.PaintedDecorator</class>
 <class>cn.edu.sdut.r314.FlooredDecorator</class>
 </decorators>
</beans>

  完整的 CDI 版本的 Decorator 示例参见:https://github.com/subaochen/weld-tutorial,具体运行方式参见其中的 README.md 文件。

4. Decorator模式实际应用

  在 Java IO API 中,其实大量的使用了 Decorator 模式。试想一下,不同的输入输出源,对输入输出数据的不同处理方式和不同流程,Decorator 模式正是大显身手的时候。基础的输入输出类就好比是毛胚房,比如下面的用法:

BufferedInputStream bis = new BufferedInputStream(new FileInputStream("filename"));

  在Java IO 模式图中可以更清楚的了解 Java IO 是如何使用 Decorator 模式的。
Java IO 模式图:

Java IO 模式图.png

  根据Java IO模式图中的Decorator 模式,我们可以实现一个 Java IO 的 Decorator,比如读入一个文件,将每个字母的 ASCII 码都后移一位,代码如下:

** EncodeInputStream:**

public class EncodeInputStream extends FilterInputStream {
 protected EncodeInputStream(InputStream in) {
 super(in);
 } 
 
 public int read() throws IOException {
 int c = super.read();
 return c + 1;
 } 
}

** 调用示例:**

public class EncodeInputStream extends FilterInputStream {
public class InputTest {
 public static void main(String[] args) {
 int c;
 try {
 InputStream in = new EncodeInputStream(new 
BufferedInputStream(InputTest.class.getResourceAsStream("test.txt")));
 while ((c = in.read()) >= 0) {
 System.out.print((char) c); 
 }
 in.close();
 } catch (IOException e) {
 e.printStackTrace();
 }
 } 
}

  执行后的输出结果是(test.txt 文件的内容是 hello,world!):ifmmp!xpsme"。Java IO 的 Decorator 在 CDI 环境下和传统环境下写法上没有多大区别,只是一般在 CDI 环境下自己写的 Decorator 可以当作组件使用,即可以通过 Decorator 的类型而不是名称查询组件而已,不再赘述。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容