“命令”设计模式也可以通过把函数作为参数传递而简化。这一模式对类的编排如图 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 提供的几个替代品。

使用一等函数对“命令”模式的重新审视到此结束。站在一定高度上看,这里采用的方式与“策略”模式所用的类似:把实现单方法接口的类的实例替换成可调用对象。毕竟,每个 Python 可调用对象都实现了单方法接口,这个方法就是 __call__