第 5 章 修饰器模式

无论何时我们想对一个对象添加额外的功能,都有下面这些不同的可选方法。

  • 如果合理,可以直接将功能添加到对象所属的类(例如,添加一个新的方法)

  • 使用组合

  • 使用继承

与继承相比,通常应该优先选择组合,因为继承使得代码更难复用,继承关系是静态的,并且应用于整个类以及这个类的所有实例。

设计模式为我们提供第四种可选方法,以支持动态地(运行时)扩展一个对象的功能,这种方法就是修饰器。修饰器(Decorator)模式能够以透明的方式(不会影响其他对象)动态地将功 能添加到一个对象中。

在许多编程语言中,使用子类化(继承)来实现修饰器模式。在 Python 中,我们可以(并且应该)使用内置的修饰器特性。

修饰器模式和 Python 修饰器之间并不是一对一的等价关系。Python 修饰器能做的实际上比修饰器模式多得多, 其中之一就是实现修饰器模式。

5.2 软件的例子

Django 框架大量地使用修饰器,其中一个例子是视图修饰器。Django 的视图(View)修饰器可用于以下几种用途:

  • 限制某些 HTTP 请求对视图的访问

  • 控制特定视图上的缓存行为

  • 按单个视图控制压缩

  • 基于特定 HTTP 请求头控制缓存

Grok 框架也使用修饰器来实现不同的目标,比如下面几种情况。

  • 将一个函数注册为事件订阅者

  • 以特定权限保护一个方法

  • 实现适配器模式

5.3 应用案例

当用于实现横切关注点(cross-cutting concerns)时,修饰器模式会大显神威。

以下是横切关注点的一些例子:

  • 数据校验

  • 事务处理(这里的事务类似于数据库事务,意味着要么所有步骤都成功完成,要么事务失败)

  • 缓存

  • 日志

  • 监控

  • 调试

  • 业务规则

  • 压缩

  • 加密

一般来说,应用中有些部件是通用的,可应用于其他部件,这样的部件被看作横切关注点。

使用修饰器模式的另一个常见例子是图形用户界面(Graphical User Interface,GUI)工具集。 在一个 GUI 工具集中,我们希望能够将一些特性,比如边框、阴影、颜色以及滚屏,添加到单个组件/部件。

5.4 实现

我们将学习如何实现一个 memoization 修饰器。所有递归函数都能因 memoization 而提速。

首先创建一个如下面的例子所示的 memoize() 函数。 这个修饰器接受一个需要使用 memoization 的函数 fn 作为输入,使用一个名为 known 的 dict 作为缓存。

import functools


def memoize(fn):
    known = dict()

    @functools.wraps(fn)
    def memoizer(*args):
        if args not in known:
            known[args] = fn(*args)
        return known[args]

    return memoizer

现在,对朴素版本的函数应用 memoize() 修饰器。这样既能保持代码的可读性又不影响性能。

@memoize
def fibonacci(n):
    '''返回斐波那契数列的第n个数'''
    assert(n >= 0), 'n must be >= 0'
    return n if n in (0, 1) else fibonacci(n-1) + fibonacci(n-2)

这一方案同时具备可读的代码和可接受的性能。此时,你可能想争论说这不是修饰器模式,因为我们并不是在运行时应用它。被修饰的函数确实无法取消修饰,但仍然可以在运行时决定是否执行修饰器。

Last updated