Python 使用一等函数实现设计模式(“命令”模式)
“命令”设计模式也可以通过把函数作为参数传递而简化。这一模式对类
的编排如图 6-2 所示。
图 6-2:菜单驱动的文本编辑器的 UML 类图,使用“命令”设计模式
实现。各个命令可以有不同的接收者(实现操作的对象)。对
PasteCommand 来说,接收者是 Document。对 OpenCommand 来说,
接收者是应用程序。
“命令”模式的目的是解耦调用操作的对象(调用者)和提供实现的对象
(接收者)。在《设计模式:可复用面向对象软件的基础》所举的示例
中,调用者是图形应用程序中的菜单项,而接收者是被编辑的文档或应
用程序自身。
这个模式的做法是,在二者之间放一个 Command 对象,让它实现只有
一个方法(execute)的接口,调用接收者中的方法执行所需的操作。
这样,调用者无需了解接收者的接口,而且不同的接收者可以适应不同的 Command 子类。调用者有一个具体的命令,通过调用 execute 方法
执行。注意,图 6-2 中的 MacroCommand 可能保存一系列命令,它的
execute() 方法会在各个命令上调用相同的方法。
Gamma 等人说过:“命令模式是回调机制的面向对象替代品。”问题是,
我们需要回调机制的面向对象替代品吗?有时确实需要,但并非始终需
要。
我们可以不为调用者提供一个 Command 实例,而是给它一个函数。此
时,调用者不用调用 command.execute(),直接调用 command() 即
可。MacroCommand 可以实现成定义了 call 方法的类。这
样,MacroCommand 的实例就是可调用对象,各自维护着一个函数列
表,供以后调用,如示例 6-9 所示。
示例 6-9 MacroCommand 的各个实例都在内部存储着命令列表
class MacroCommand:"""一个执行一组命令的命令"""def __init__(self, commands):self.commands = list(commands) # ➊def __call__(self):for command in self.commands: # ➋
command()
❶
❶ 使用 commands 参数构建一个列表,这样能确保参数是可迭代对象,
还能在各个 MacroCommand 实例中保存各个命令引用的副本。
❷ 调用 MacroCommand 实例时,self.commands 中的各个命令依序执
行。
复杂的“命令”模式(如支持撤销操作)可能需要更多,而不仅是简单的
回调函数。即便如此,也可以考虑使用 Python 提供的几个替代品。
-
像示例 6-9 中 MacroCommand 那样的可调用实例,可以保存任何所
需的状态,而且除了 call 之外还可以提供其他方法。 -
可以使用闭包在调用之间保存函数的内部状态。
使用一等函数对“命令”模式的重新审视到此结束。站在一定高度上看,
这里采用的方式与“策略”模式所用的类似:把实现单方法接口的类的实
例替换成可调用对象。毕竟,每个 Python 可调用对象都实现了单方法接
口,这个方法就是 call。