首页 > 代码库 > 9.4.1 函数式和命令式类

9.4.1 函数式和命令式类

9.4.1 函数式和命令式类 

 

在类或者类的构造函数的参数值中的 let 绑定,就像我们在其他 F# 代码中看到的 let 绑定一样,也是不可变值;此外,使用 member 关键字声明的属性,创建的是只读属性(只有 getter)。因此,如果类只引用其他不可变类型的值,那么,类也不可变。

比方说,在前面的例子中,我们要允许改变客户的收入,可以有两种方式:

■以纯函数式风格,对象将返回一个新实例,收入是更新后的,其他所有属性都是原始值。

■使用命令式风格,收入将是可变字段。

清单 9.15 展示了类的函数式版本(ClientF),以及命令式的类,名为ClassI。

 

清单 9.15 Client 类型的函数式和命令式版本 (F#)

 

type ClientF(name, inc) = 

  member x.Name = name   | [1]

  member x.Income = inc   |

 

  member x.WithIncome(v) =   [2]

    new ClientF(name, v)

 

  member x.Report() = 

    printfn "%s %d" name inc

type ClientI(name, inc) = 

  let mutable inc = inc    [3]

 

  member x.Name = name 

  member x.Income 

    with get() = inc      | [4]

    and set(v) = inc <- v  |

 

  member x.Report() = 

    printfn "%s %d" name inc

 

在函数式版本中,所有属性都是只读的[1]。s 改变客户的收入,必须创建客户的一个新实例,使用 WithIncome 方法[2]很容易做到,它返回对象的副本,收入为新设置的值。

命令式版本使用 mutable 关键字[3],声明了可更新的字段,保存收入。声明字段,我们使用的名字,与值和构造函数的参数相同;新的值隐藏了原来的值,这样,我们再也不能访问原始值了。初看起来,这可能很奇怪,但是,可以防止原打算使用当前值(可能已经更改)时,而意外使用了初始值。

在命令式版本中,另一个值得注意的是用来改变客户收入的读/写属性,这个属性是由两个成员组成,类似于方法声明。get 成员没有任何参数,只返回值;而 set 成员只有一个参数,表示新值的,返回结果为 unit。虽然这个语法与 C# 属性声明略有不同,但原理相同。

当改变客户收入时,两个类的用法不同,但是,两个版本得到的结果相同。可以在清单 9.16 中看到 F# Interactive 会话,演示如何使用这两个类。

 

清单 9.16 Client 类型的函数式和命令式编程 (F# Interactive)

> let joeOriginal = newClientF("Joe", 30);; 

val joeOriginal : ClientF

 

> let joeUpdated =joeOriginal.WithIncome(40);;   [1]<-- 创建客户的新实例

val joeUpdated : ClientF

 

> joeUpdated.Income;; 

val it : int = 40

 

> let joeMutable = newClientI("Joe", 30);; 

val joeMutable : ClientI

 

> joeMutable.Income <- 40;;   [2] <-- 修改已有的实例

val it : unit = ()

 

> joeMutable.Income;; 

val it : int = 40

 

当使用不可变的版本时[1],我们把返回的客户值,用新的名字来保存。这样,我们仍然可以访问原始值。如果在后面的代码中,我们不想访问原始实例,就可以使用值隐藏,两个值使用相同的名字。在命令式版本中,可以使用读/写属性[2],改变收入。我们使用 <- 运算符,就像在标准的 .NET 库中使用其他声明的对象一样。

虽然我们重点在于函数式编程,但是,知道如何写这样的可变类,有时也是有用的。如果需要公开大段的 F# 代码给 C# 客户端,可能要把代码包装到至少一个类中,因为,这样更方便从 C# 中使用。在这一点上,可以随便选择一种风格:命令式风格,有一些可变类型;纯函数式风格,所有都是可变的。从 F# 的观点来看,第二种方法是更清晰的;但对于开发人员来说,不习惯于处理完全组合不可变类型的库,可能会发现使用可变状态的包装会更容易。

我们几乎准备展示从 C# 调用 F# 代码的完整示例子了,但还需要先完成面向对象的 F# 功能之旅。

9.4.1 函数式和命令式类