8.2.2 命令设计模式(The command design pattern)
命令模式(commandpattern)所描述的方法,表示在应用程序中的动作,相对于前一个模式,它常用于参数化已知的行为(比如,筛选列表)但缺少部分(条件),而命令模式经常保存某些“工作单元”,能够在以后调用。我们经常看到命令集合,用来描述用户可以选择处理或操作的步骤。看一下图8.2,就会认识到,接口看起来像可以用函数替换的很好候选。
图8.2 Invoker 保存实现 Command 接口类的集合。当调用时,具体命令使用 Receiver 对象,它通常携带并修改了某些状态。
能够很容易地用函数替换的类型是 Command 接口。另外,它还有一个方法,承担揭示作用。实现接口(比如,ConcreteCommand)的类可以转换成函数,既可以使用 lambda 函数的语法来构造,更复杂时,还可以写作普通函数。
我们提到过,命令模式和策略模式之间的区别是,Invoker 处理命令列表,并在需要时执行它们,非常相似于客户贷款的例子。我们有一个测试集合,用来检查客户是否合适,但我们的函数版本没有声明 Command 接口,而是使用 C# 中的 Func<Client, bool>委托,和 F# 中的函数类型Client -> bool。调用是 TestClient 方法,它使用测试来检查客户。
注意
我们解释过,Receiver类,通常表示某种状态,在命令调用时可以改变,如图8.2所示。在一个典型的面向对象程序中,这可能是应用程序状态的一部分;在图形编辑器中,我们可以使用命令表示撤消历史记录,那时,状态就是能够应用撤消步骤的图片。
这并不是在函数编程中使用模式的方式,命令通常返回某种结果(比如,在我们的客户检查示例中的布尔值),而不是修改状态。在函数编程中,Receiver 可以是 lambda 函数捕获的值。
在函数式编程中,虽然通常应该避免使用可变状态,但是,有示例说明它还是有用的,即使在 F# 中。我们将看到,类似于命令模式的技术可以有助于对外隐藏状态,如果我们想保持大多数程序是纯函数式,这是很重要的。首先看一下 C# 中的类似做法,然后,再讨论在 F# 中,使用 lambda 函数的通常实现。