浅谈装饰模式及其在JDK、Flink中的应用

前言

上周末在家翻看之前写的部分文章,发现在设计模式方面甚少涉猎。在阅读开源项目源码的过程中,我们经常会接触到各种设计模式,深入理解它们无疑大有裨益,能够帮助我们快速get到那些masterminds背后的思想。今天就来谈一谈应用较为广泛的装饰模式

装饰模式与四要素

装饰模式属于GoF设计模式分类中的结构型模式。

所谓装饰模式(decorator pattern),就是在不改变原有类,也不影响其他继承自该类的子类的行为的基础上,为原有类在运行期动态地添加新行为的模式。(Attach additional responsibilities to an object dynamically)

我们知道,类继承是为类扩充功能的一般方案。而装饰模式作为类继承的替代方案存在(Decorators provide a flexible alternative to subclassing for extending functionality),其意义在于:

类继承扩充的功能在编译期就被确定,而装饰模式扩充的功能可以在运行时由调用方确定。如果要为类同时扩充多个相互独立而又可以组合的功能,采用类继承方案就意味着为每种组合创建新的类,容易造成子类泛滥。装饰模式就可以灵活地按需组合(就像现实中的小装饰品可以随意摆放一样),更加简洁且易于修改。

下面的UML类图示出实现装饰模式的四要素。

  • 构件(Component):接口,用于定义整个实体空间的最基础的行为规范;
  • 构件实体(ConcreteComponent):实现Component的实体类,本身具有一些功能,同时也是被装饰(被扩充)的类;
  • 装饰器(Decorator):实现Component的类,其中维护一个ConcreteComponent的实例,具体的装饰功能由其子类实现;
  • 装饰器实体(ConcreteDecorator):继承Decorator并实现具体的装饰功能。

通过下面两句话即可使用装饰器实体ConcreteDecorator实现的扩充功能:

Component component = new ConcreteDecorator(new ConcreteComponent());
component.operation();

可见,调用方只需要额外调用装饰器实体的构造函数,而不必关心Component/ConcreteComponent在装饰之后的变化。不过由上也可以看出,装饰模式会new出更多的对象,当装饰器实体的链比较长时会有性能问题,并且出现问题时也不利于debug。

上面所有内容讲的装饰模式叫做透明装饰模式,即用户总可以只用Component来调用所有功能。相对地,还有一种半透明装饰模式,即装饰器实体中允许存在Component中不存在的新方法(如someNewBehavior()),调用方式相应就变成:

ConcreteDecorator component = new ConcreteDecorator(new ConcreteComponent());
component.someNewBehavior();

由于扩充功能可以在新方法中定义,半透明装饰模式更加灵活,但是就无法对用户屏蔽ConcreteDecorator存在的现实了。更重要的是,半透明装饰模式下对实例进行多次(链式)装饰是没有意义的,因为只能调用最后一次装饰时装饰器实体的新增方法。

干说了这么多,举两个示例来帮助理解吧。

Java I/O中的装饰模式

装饰模式在java.io包中广泛使用,包括基于字节流的InputStream/OutputStream和基于字符的Reader/Writer体系。以下以InputStream为例。

InputStream是所有字节输入流的基类,其下有众多子类,如基于文件的FileInputStream、基于对象的ObjectInputStream、基于字节数组的ByteArrayInputStream等。有些时候,我们想为这些流加一些其他的小特性,如缓冲、压缩等,用装饰模式实现就非常方便。相关的部分类图如下所示。

这个类图很标准,其中:

  • 构件是InputStream;
  • 构件实体是FileInputStream、ObjectInputStream等等;
  • 装饰器是FilterInputStream;
  • 装饰器实体是FilterInputStream的所有子类。

观察一下装饰器FilterInputStream的开头,可以发现它持有InputStream的引用,并且实现了InputStream中的所有方法(实际上就是简单地代理了一下)。具体的装饰器实体就继承FilterInputStream,并实现对应的扩充功能。如下图所示。

以下就可以用BufferedInputStream和GZIPInputStream创建一个带缓冲、压缩的文件输入流。

InputStream is = new GZIPInputStream(new BufferedInputStream(new FileInputStream("test.txt")));

当然,如果我们想要自己实现一个InputStream的装饰器实例,创建一个FilterInputStream的子类即可,就不再举例了。

Flink State TTL中的装饰模式

笔者之前写过一篇文章《简析Flink状态生存时间(State TTL)机制的底层实现》,这里就用到了装饰模式,但不像Java I/O那样标准。

为状态增加TTL的特性可以直接在原始状态之上实现,因此符合装饰模式的场景。Flink引入了一个AbstractTtlDecorator<T>抽象类作为装饰器,负责为状态类型T装饰上与TTL相关的基本逻辑。相关的部分类图如下所示。

可见,虽然AbstractTtlDecorator并未持有State的实例(只有State的类型参数),但是在其子类AbstractTtlState中,通过持有TTL状态上下文TTLStateContext间接地得到了State实例。例如,由AbstractTtlState派生出来的TtlMapState直接在原来的MapState上进行增删改查操作,只是附带上了AbstractTtlDecorator和AbstractTtlState提供的TTL逻辑而已。其他的TtlListState等也是同理。具体的代码可参见前面给的传送门,这里不再重复贴了。

虽然这种模式的类结构并不典型,但是也完全契合装饰模式的精神,Ttl*State对用户也是透明的。有很多开源框架都采用了这种相对松散的装饰模式,有时会被称为包装(Wrapper)模式。

The End

明天高考,各位小盆友加油加油。

民那晚安晚安。

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