- 人人都懂设计模式:从生活中领悟设计模式(Python实现)
- 罗伟富
- 3005字
- 2025-04-14 19:58:43
第4章 装饰模式
4.1 从生活中领悟装饰模式
4.1.1 故事剧情—你想怎么搭就怎么搭
Tony因为换工作而搬了一次家!这是一个4室1厅1卫1厨的户型,住了4户人家。恰巧这里住的都是年轻人,有男孩也有女孩,而 Tony就是在这里遇上了自己喜欢的人,她叫Jenny。Tony和Jenny每天低头不见抬头见,但Tony是一个程序员,天生不善言辞,不懂着装,老被Jenny嫌弃:满脸猥琐,一副邋遢样!
被嫌弃后,Tony痛定思痛:一定要改善一下自己的形象!于是叫上自己的死党Henry一起去了五彩城……
Tony在这个大商城中兜兜转转,被各个商家教化着该怎样搭配衣服:衬衫要套在腰带里面,风衣不要系纽扣,领子要立起来……
在反复试穿了一个晚上的衣服之后,Tony 终于找到一套还算凑合的着装:下面是一条卡其色休闲裤配一双深色休闲皮鞋,加一条银色针扣头的黑色腰带;上面是一件紫红色针织毛衣,内套一件白色衬衫;头上戴一副方形黑框眼镜。整体着装虽不潮流,却透露出一种工作人士的成熟、稳健和大气!

4.1.2 用程序来模拟生活
服装店里的衣服品类齐全,款式多样,但不同品味的人会搭配出完全不同的风格。Tony是一个程序员,给自己搭配了一套着装,但类似的着装也可以穿在其他人身上,比如一个老师也可以这样穿。下面我们就用程序来模拟这样一个情景。
源码示例4-1 模拟故事剧情




测试代码:

上面的测试代码中decorateTeacher=GlassesDecorator(WhiteShirtDecorator(LeatherShoesDecorator (Teacher("wells","教授"))))这个写法,大家不要觉得奇怪,它其实就是将多个对象的创建过程合在了一起,是一种优雅的写法。创建的Teacher对象通过参数传给LeatherShoesDecorator的构造函数,而创建的LeatherShoesDecorator对象又通过参数传给WhiteShirtDecorator的构造函数,依此类推……
输出结果:

4.2 从剧情中思考装饰模式
4.2.1 什么是装饰模式
Attach additional responsibilities to an object dynamically.Decorators provide a flexible alternative to subclassing for extending functionality.
动态地给一个对象增加一些额外的职责,就拓展对象功能来说,装饰模式比生成子类的方式更为灵活。
就故事剧情中这个示例来说,由结构庞大的子类继承关系(如图4-1所示)转换成了结构紧凑的装饰关系(如图4-2所示)。

图4-1 继承关系

图4-2 装饰关系
4.2.2 装饰模式设计思想
在故事剧情中,Tony为了改善自己的形象,换了整体着装,改变了自己的气质,使自己看起来不再是那个猥琐的邋遢样。俗话说一个人帅不帅,三分看长相,七分看打扮。同一个人,不一样的着装,会给人完全不一样的感觉。我们可以任意搭配不同的衣服、围巾、裤子、鞋子、眼镜、帽子以达到不同的效果。
在这个追求个性与自由的时代,穿着的风格可谓是开放到了极致,真是你想怎么搭就怎么搭!如果你去参加一个正式会议或演讲,可以穿一套标配西服;如果你去大草原,想骑着骏马驰骋天地,便该穿上马服、马裤、马鞋;如果你是漫迷,去参加动漫节,亦可穿上cosplay的衣服,让自己成为那个内心向往的主角……
这样一个时时刻刻发生在我们生活中的着装问题,就是程序中装饰模式的典型样例。在程序中,我们希望动态地给一个类增加额外的功能,而不改动原有的代码,就可用装饰模式来进行拓展。
4.3 装饰模式的模型抽象
4.3.1 类图
装饰模式的类图如图4-3所示。

图4-3 装饰模式的类图
图4-3中的Component是一个抽象类,代表具有某种功能(function)的组件,ComponentImplA和ComponentImplB分别是其具体的实现子类。Decorator是Component的装饰器,里面有一个Component的对象decorated,这就是被装饰的对象,装饰器可为被装饰对象添加额外的功能或行为(addBehavior)。DecoratorImplA和DecoratorImplB分别是两个具体的装饰器(实现子类)。
这样一种模式很好地将装饰器与被装饰的对象进行了解耦。
4.3.2 Python中的装饰器
在Python中一切都是对象:一个实例是一个对象,一个函数也是一个对象,甚至类本身也是一个对象。在Python中,可以将一个函数作为参数传递给另一个函数,也可以将一个类作为参数传递给一个函数。
1.Python中函数的特殊功能
在 Python 中,函数可以作为一个参数传递给另一个函数,也可以在函数中返回一个函数,还可以在函数内部再定义函数。这是Python和很多静态语言不同的地方,这一特性给它带来了很多新奇的功能。
源码示例4-2 函数的特殊功能

输出结果如下:

上面的调用代码等同于:


2.装饰器修饰函数
装饰器的作用:包装一个函数,并改变(拓展)它的行为。
我们以一个场景为例,看一下Python中装饰器是如何实现的。假设有这样一个需求:我们希望每一个函数在被调用之前和被调用之后,记录一条日志。
源码示例4-3 定义装饰器

输出结果:

我们在loggingDecorator中定义了一个内部函数wrapperLogging,用于在传入的函数中执行前后记录日志,一般称这个函数为包装函数,并在最后返回这个函数。我们称loggingDecorator为装饰器,定义这个装饰器函数之后,就可以将其应用于所有希望记录日志的函数,比如下面这样一个函数:

输出结果:

有没有发现,我们每次调用一个函数,都要写两行代码。这是非常繁琐的,Python有没有更简单的方式,让我们的代码更简洁一些呢?答案是肯定的,那就是@decorator语法,如下所示:

@loggingDecorator 表示用loggingDecorator装饰器来修饰showMin函数,它的功能与下面代码的作用是相同的,但调用时,只需要写一行代码,和调用一般函数是一样的。

3.装饰器修饰类
装饰器可以是一个函数,也可以是一个类(必须要实现__call__方法,使其是callable的)。同时装饰器不仅可以修改一个函数,还可以修饰一个类,示例如下。
源码示例4-4 修饰类的装饰器


输出结果:

这里 ClassDecorator 是类装饰器,记录一个类被实例化的次数。其修饰一个类和修饰一个函数的用法是一样的,只需在定义类时 @ClassDecorator即可。
4.3.3 模型说明
1.设计要点
(1)可灵活地给一个对象增加职责或拓展功能。你可任意地穿上自己想穿的衣服。不管穿上什么衣服,你还是那个你,但穿上不同的衣服你就会有不同的外表。
(2)可增加任意多个装饰 你可以只穿一件衣服,也可以只穿一条裤子,也可以衣服和裤子搭配着穿,随你意!
(3)装饰的顺序不同,可能产生不同的效果。在上面的示例中,Tony把针织毛衣穿在外面,白色衬衫穿在里面。当然,如果你愿意(或因为怕冷),也可以把针织毛衣穿在里面,白色衬衫穿在外面。但两种着装穿出来的效果、给人的感觉肯定是完全不一样的。
使用装饰模式时,想要改变装饰的顺序,也是非常简单的。只要把测试代码稍微改动一下即可,如下所示:

输出结果如下:

2.装饰模式的优缺点
优点:
(1)使用装饰模式来实现扩展比使用继承更加灵活,它可以在不创造更多子类的情况下,将对象的功能加以扩展。
(2)可以动态地给一个对象附加更多的功能。
(3)可以用不同的装饰器进行多重装饰,装饰的顺序不同,可能产生不同的效果。
(4)装饰类和被装饰类可以独立发展,不会相互耦合;装饰模式相当于继承的一个替代模式。
缺点:
与继承相比,用装饰的方式拓展功能容易出错,排错也更困难。对于多次装饰的对象,调试寻找错误时可能需要逐级排查,较为烦琐。
3.Python装饰器与装饰模式的区别与联系
在“4.3.2 Python中的装饰器”一节中讲了Python中装饰器的原理和用法,它与我们在这一章讲的装饰模式的设计模式有什么区别呢?二者的区别如表4-1所示。
表4-1 Python装饰器与装饰模式的区别

二者的联系是,设计的思想相似,即要达到的目标是相似的:更好的拓展性,以及在不需要做太多代码变动的前提下,增加额外的功能。
4.4 应用场景
(1)有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长时。
(2)需要动态地增加或撤销功能时。
(3)不能采用生成子类的方法进行扩充时,类的定义不能用于生成子类(如Java中的final类)。
装饰模式的应用场景非常广泛。如在实际项目开发中经常看到的过滤器,便可用装饰模式的方式实现。如果你是Java程序员,那么你对I/O中的FilterInputStream和FilterOutputStream一定不陌生,它的实现其实就是一个装饰模式。FilterInputStream(FilterOutputStream)就是一个装饰器,而InputStream(OutputStream)就是被装饰的对象。我们看一下创建对象的过程:

这个写法与上面Demo中的decorateTeacher=GlassesDecorator(WhiteShirtDecorator(LeatherShoesDecorator(Teacher("wells","教授"))))是不是很相似?它们都是用一个对象套一个对象的方式进行创建的。