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

时间:2022-10-12 09:16:13

在前两篇博客中详细的介绍了"策略模式"和“观察者模式”,今天我们就通过花瓶与鲜花的例子来类比一下“装饰模式”(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

设计模式(三):“花瓶+鲜花”中的装饰者模式(Decorator Pattern)的更多相关文章

  1. 浅谈设计模式--装饰者模式(Decorator Pattern)

    挖了设计模式这个坑,得继续填上.继续设计模式之路.这次讨论的模式,是 装饰者模式(Decorator Pattern) 装饰者模式,有时也叫包装者(Wrapper),主要用于静态或动态地为一个特定的对 ...

  2. 设计模式学习--装饰者模式(Decorator Pattern)

    概念: 装饰者模式(Decorator Pattern): 动态地将功能添加到对象,相比生成子类更灵活,更富有弹性. 解决方案: 装饰者模式的重点是对象的类型,装饰者对象必须有着相同的接口,也也就是有 ...

  3. 设计模式 - 装饰者模式(Decorator Pattern) Java的IO类 用法

    装饰者模式(Decorator Pattern) Java的IO类 用法 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26716 ...

  4. 设计模式 - 装饰者模式(Decorator Pattern) 具体解释

    装饰者模式(Decorator Pattern) 具体解释 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26707033 装饰者 ...

  5. C#设计模式——装饰者模式(Decorator Pattern)

    一.例子在软件开发中,我们往往会想要给某一类对象增加不同的功能.比如要给汽车增加ESP.天窗或者定速巡航.如果利用继承来实现,就需要定义无数的类,Car,ESPCar,CCSCar,SunRoofCa ...

  6. 设计模式(三)——装饰器模式(Decorator Pattern)

    发现太过于刻意按照计划来写博客,有点不实际,刚好最近在一个网课上复习AOP的知识,讲到了装饰器模式和代理模式,顺便复习总结一下. 首先了解一下装饰器模式,从名字里面可以看出来,装饰器模式就类似于房子装 ...

  7. 23种设计模式之装饰器模式(Decorator Pattern)

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

  8. Android设计模式之中的一个个样例让你彻底明确装饰者模式(Decorator Pattern)

    导读 这篇文章中我不会使用概念性文字来说明装饰者模式.由于通常概念性的问题都非常抽象.非常难懂.使得读者非常难明确究竟为什么要使用这样的设计模式.我们设计模式的诞生,肯定是前辈们在设计程序的时候遇到了 ...

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

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

随机推荐

  1. iOS开发系列--地图与定位

    概览 现在很多社交.电商.团购应用都引入了地图和定位功能,似乎地图功能不再是地图应用和导航应用所特有的.的确,有了地图和定位功能确实让我们的生活更加丰富多彩,极大的改变了我们的生活方式.例如你到了一个 ...

  2. 纯HTML5&plus;CSS3制作生日蛋糕

    以一个前端开发的身份绘制一个简单的蛋糕庆祝一下今天这个好日子吧,程序员庆生的乐趣与哀愁啊.写的比较简陋,感兴趣的看一下吧. 先发个效果图吧 蛋糕分为三个部分,底部蛋糕,顶层蛋糕和蜡烛部分.HTML的布 ...

  3. sea&period;js&comma;spm学习

    安装spm 下载sea.js 运行spm npm install spm@2.x -g npm install spm-build -g 下载sea.js git clone https://gith ...

  4. poj 3308 &lpar;最大流&rpar;

    题意:n*m的地图,给出L个火星人登陆的坐标,要在火星人登陆地球的瞬间全部消灭他们,有一种激光枪,一次可以消灭一行(或一列),消灭一行(或一列)有不同的代价,总代价是所有激光枪的代价之积. 思路:之前 ...

  5. ubuntu安装Java jdk1&period;7&period;0

    1.下载JDK  http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html 2.解压 3. ...

  6. java8&lowbar;api&lowbar;stream

    与集合联系紧密 Stream-1    stream概念    特点    使用示例

  7. gitolite migration to bitbucket

    https://gist.github.com/kostajh/9249937 https://designhammer.com/blog/easily-migrate-git-repositorie ...

  8. CF1133E K Balanced Teams&lpar;DP&rpar;

    /* 排序之后每个点往前能选择的是一段区间, 所以我们实际上转移位置是确定的 然后f[i][j]表示到了i选了j段的最大贡献, 显然状态数是O(n^2)的, 转移是O(1)的 */ #include& ...

  9. Websocket实现群聊、单聊

    Websocket 使用的第三方模块:gevent-websocket 群聊 ws群聊.py中的内容 from flask import Flask, request, render_template ...

  10. ubuntu下使用g&plus;&plus;编译时默认支持C&plus;&plus;11 配置方法

    1.只需要在源文件程序中加上如下一行代码: #pragma GCC diagnostic error "-std=c++11" 此时源文件代码如下: #pragma GCC dia ...