设计模式(三):“花瓶+鲜花”中的装饰者模式(Decorator Pattern)

在前两篇博客中详细的介绍了"策略模式"和“观察者模式”,今天我们就通过花瓶与鲜花的例子来类比一下“装饰模式”(Decorator Pattern)。在“装饰模式”中很好的提现了开放关闭原则,即类应该对扩展开放对修改关闭。装饰者模式可以让我们在不对原来代码的修改的情况下对类进行扩展。这也好比我们往花瓶里插花,我们在插花的时候是不会对花瓶以及原来的话进行任何的修改,而只管将我们新的花添加进花瓶即可。这就是我们的装饰者模式。当然本篇博客中所采用的语言仍然是Swift语言。

装饰者模式,用另一种表达方式就是“对原有的物体进行装饰,给原有的物体添加上新的装饰品”。举个栗子,比如一个礼物,我们要对其进行包装,礼物是被装饰者(我们称为组件---Component),而包装盒以及包装盒上的花等等就是装饰品(我们成为装饰者---Decorator)。如果换成花瓶与鲜花的关系,花瓶就是Component,而鲜花就是Decorator。下方引用了装饰者模式的定义:

装饰者模式:动态地将责任附加到对象上。若要扩展功能,装饰着提供了比继承更有弹性的替代方案。

一、使用“类图”分析鲜花+花瓶的装饰关系

与之前博客的风格类似,我们还是依托于实例来理解“装饰者模式”,我们就依托于花瓶与鲜花的关系来理解一下装饰者模式。在之前的博客中我们提到过一条设计原则“封装变化”,也就是说要将变化的东西进行封装提取。在“装饰者模式”中所使用的装饰就是变化的部分,也就是Decorator是变化的部分对应着我们的鲜花,因为往花瓶中插花的过程就是鲜花变化的过程,也就是为花瓶装饰的过程。而花瓶就是组件了。

在“装饰者模式”中需要注意的是,这里所谓的装饰者不单单就是我组件添加的新的装饰品。一个装饰者对象就是添加该装饰后的组件,也就是说装饰者=旧组件 + 新装饰品,理解这一点是非常重要的。具体请看下方的组件与装饰者之间的关系:

设计模式(三):“花瓶+鲜花”中的装饰者模式(Decorator Pattern)

下方的类图就是我们将要实现的“装饰者模式”的实例,也就是鲜花和花瓶的关系。下方所有类的基类是VaseComponent(花瓶组件),在VaseComponent类中的description字段是用来描述某某花瓶中装有某某花的,display()方法用来打印description描述信息的。上方的红框就是所有的鲜花(装饰者---Decorator),所有鲜花装饰者的基类是FlowerDecorator,当然FlowerDecorator也是继承自VaseComponent(花瓶组件)的,因为装饰者拥有被装饰的对象同时又有新添加的装饰物(见上图)。在这些装饰者类中包含一个字段,该字段就是VaseComponent的对象(花瓶组件的对象)。该对象可以指没有任何装饰的花瓶,也可以指已经添加了装饰的花瓶。无论是装饰者还是被装饰者都有共同的基类,所以我们就可以利用多态来实现“装饰者模式”。

设计模式(三):“花瓶+鲜花”中的装饰者模式(Decorator Pattern)

在上面类图的FlowerDecorator中的VaseComponent对象就对应着本部分第一张图中装饰者中包括的那个组件。该组件是最近一次装饰过的组件,而装饰者所负责的事情就是在该组件上添加上该装饰者特有的装饰品。换句话说,此时的装饰者的对象就是最新的组件对象。也许经过这些原理的讲解,你会有些迷惑,那么不要着急,在具体代码实现时会带你拨开云雾见日出的。

二、“花瓶+鲜花”具体代码实现(Swift版)

当然,我们此处的代码实现与上面的“类图”的设计是一致的。看完代码再结合着上面的“类图”你会对装饰者模式有更好的理解。下方我们会一步步的给出代码具体实现,当然下方的类名,成员变量以及成员方法的命名与上述类图一直。

1.实现空花瓶的基类(VaseComponent)

花瓶的基类VaseComponent就是我们被修饰者也就是我们所有花瓶(组件)的基类了。当然VaseComponent不仅仅是所有花瓶的基类,它还是所有装饰者的基类,因为装饰者对象 = 旧组件 + 新装饰品 = 新组件。在该类中的description字段中存储的是花瓶的描述信息,比如“瓷花瓶”,“玻璃花瓶”等信息。getDescription()->String方法是用来获取description存储的描述信息的。display()->Void方法就是对getDescription()方法获取到的值进行打印, 具体实现如下所示。

设计模式(三):“花瓶+鲜花”中的装饰者模式(Decorator Pattern)

2.创建我们的空花瓶

在第一步中我们创建了空花瓶的基类,紧接着我们要实现具体的花瓶。在下方代码中我们创建了两个空花瓶,一个是Porcelain瓷花瓶,一个是Glass玻璃花瓶。并且在调用父类初始化器时为父类中的description字段进行初始化。空花瓶比较简单,代码也不多,空花瓶就是一个坯子,等着其他鲜花来做修饰,具体实现如下所示。

设计模式(三):“花瓶+鲜花”中的装饰者模式(Decorator Pattern)

3. 鲜花基类(FlowerDecorator)的实现

FlowerDecorator也就是所有装饰者的基类,这里与其称为鲜花基类,还不如成为所有“新花瓶”的基类。什么是“新花瓶”呢?我们暂且成为添加了新的鲜花种类的花瓶为“新花瓶”。FlowerDecorator是所有鲜花装饰者的基类,而FlowerDecorator继承自VaseComponent类。在FlowerDecorator中添加了一个vase字段,该字段是VaseComponent类型,用于存储“旧组件”,也就是上一次被修饰过的花瓶组件。这也是所有装饰者都包含的字段,“装饰者”在初始化时会指定上次被修饰后组件(空花瓶或者其他修饰者的对象)。也就是说vase字段中存储的可以是一个空的花瓶对象,也可以是其他“装饰者”类的对象。具体实现如下所示:

设计模式(三):“花瓶+鲜花”中的装饰者模式(Decorator Pattern)

4.实现各个装饰者(Decorator)

上方我们已经创建好了装饰者的基类,在装饰者基类中含有最新的组件(花瓶的状态,还有多少种类的花)。而在“装饰者”的类中我们要将具体花的品种,也就是我们变化的部分添加进具体的装饰者的实现中。下方的第一个类是我们的玫瑰花Rose类,重写了基类的getDescription()方法,在该方法中,为上一个装饰者添加了新的装饰品,也就是“玫瑰”。而在百合花Lily的类中我们为组件添加了“百合”装饰品。具体实现方式如下所示:

设计模式(三):“花瓶+鲜花”中的装饰者模式(Decorator Pattern)

三、“万事俱备,只欠东风”--创建测试用例

经过上面的两大步,我们的装饰者模式的代码实现也就做完了。但是上面只是实现,没有测试用例的驱动,上面的示例看上去不够直观。为了搞清楚其工作方式,我们的测试用例还是必不可少的。下方就是我们的测试用例的代码:

设计模式(三):“花瓶+鲜花”中的装饰者模式(Decorator Pattern)

在上述代码中呢,我们首先创建了一个空的瓷花瓶的对象procelain,紧接着打印描述信息(输出“瓷花瓶”)。然后为该瓷花瓶的对象procelain添加上Rose和Lily装饰。当然我们仍然使用procelain变量来接收添加Rose修饰后的对象(也就是Rose类的对象),此时Rose类的对象代表着“插有玫瑰花的瓷花瓶”。紧接着,我们在Rose对象的基础上添加了Lily装饰,添加Lily装饰后,porcelain就是Lily类的对象,表示“插有玫瑰花,百合花的瓷花瓶”。最后调用display()方法打印最新的描述信息。

在上述测试用例中,我们为porcelain对象添加了两个装饰品,最终的porcelain对象是Lily的对象,它是空瓷瓶+玫瑰+百合花的组合体。当最终该测试用例的Lily对象porcelain调用display()方法时,在display()方法中会调用该对象中的getDescription()方法,而该对象中的getDescription()方法会调用上一个修饰者(此处是Rose)对象的getDescription()方法,最终会找到我们的组件,也就是我们的空瓶子(Porcelain)中的的getDescription()方法。具体调用方式如下图所示:

设计模式(三):“花瓶+鲜花”中的装饰者模式(Decorator Pattern)

今天关于“装饰者模式”的完整实例就先到这。

同样,在本篇博客的末尾,我们给出类本篇博客是Dmeo, Github: https://github.com/lizelu/DesignPatterns-Swift

上一篇:ContentProvider工作原理


下一篇:java时间类简单总结