首页 > 代码库 > 9.2.2 使用接口对象类型

9.2.2 使用接口对象类型

9.2.2 使用接口对象类型

 

就像记录和差别联合一样,接口类型使用 type 构造来声明。清单 9.8 显示了把以前的检查记录类型,转换为接口类型。

 

清单 9.8 用接口表示客户检查 (F#)

type ClientTest =

  abstract Check : Client –> bool

  abstract Report : Client -> unit

 

在清单 9.8 中的声明是说,实现 ClientTest 接口的任何类型,都将需要提供两个成员。在接口声明中,成员使用 abstract 关键字,表示还没有实现;声明只描述了成员的名字和类型签名。我们没有明确说要声明接口,但是,F# 编译器非常聪明,能够推断出来。例如,如果我们提供了成员的一个实现,编译器就能知道,我们要声明抽象类。在通常的 F# 编程实践中,我们很少需要抽象类和实现继承,因此,在这一章,我们将重点放在接口的使用上。

在我们进一步讨论具体实现之前,需要注意一点:在 F# 中,声明接口时,不使用 I 前缀,F# 有办法声明类型,并试图统一所有的类型(例如,在声明差别联合时,不使用 D 做前缀,等等)。在写纯粹的 F# 代码时,我们可以打破 .NET 的规则;但是,在写 F# 库,准备提供给其他 .NET 语言使用时,在所有公共 API 中,都应该始终遵循所有标准的 .NET 编码约定(例如,接口使用 I 做前缀)。

如果我们想创建检查,也就是说,在 C# 中检测客户的犯罪记录,就必须写一个新的类,实现接口。F# 也支持类,但还提供了另一种方法,称为对象表达式(object expressions)。这是受函数编程的启发,且通常更优雅,因为,我们在创建有用的值之前,不必要写任何类的声明。

 

对象表达式和 lambda 函数

 

在解释什么是对象表达式时,类比一下接口和函数类型,是非常有用的。函数类型的签名用抽象的概念描述,说明函数需要什么参数,返回什么类型的结果;函数的具体代码在创建函数值时提供,这可以使用 lambda 函数实现,它就是返回函数的表达式,或者是创建命名函数的 let 绑定。

接口是对象的抽象描述。(1)它定义了对象应有的成员,(2)并描述成员的类型。另外,我们在创建具体值时,要为成员提供实际的代码。一种选择是写命名类,实现接口,这类似于创建命名函数。对象表达式类似于 lambda 函数,它们可以用在代码的任何地方,创建实现了接口的值,而不必为提供实际代码的类型指定名字。如果熟悉 Java 的话,F# 中的对象表达式在本质上与 Java 中的匿名类(anonymous classes)是相同的。

 

在清单 9.9 中,我们将创建测试,检查客户的犯罪记录和收入,并创建接口值的列表,就像我们先前创建的记录列表一样。

 

清单 9.9 使用对象表达式创建接口 (F# Interactive)

> let testCriminal =

     { new ClientTestwith    [1]

       member x.Check(cl) = cl.CriminalRecord = true   |

       member x.Report(cl) =                      | [2]

         printfn "‘%s‘ has a criminal record!" cl.Name };;   |

val testCriminal : ClientTest    [3]

 

> let testIncome =

     { new ClientTestwith

       member x.Check(cl) = cl.Income < 30000 <-- 实现检查客户

       member x.Report(cl) =    <-- 实现报表

         printfn "Income of ‘%s‘ is less than 30000!" cl.Name };;

val testCriminal : ClientTest

 

> let tests = [ testCriminal; testIncome];;    <-- 创建接口值的列表

val tests : ClientTest list

 

这段代码使用对象表达式,创建了两个值,实现了 ClientTest 的接口类型;每个对象表达式括在大括号中,前面的标题[1]描述了要实现的接口,后面跟关键字 with,然后,是成员的声明[2]。在语法上,这与我们在上一节讨论的类型扩展非常相似。成员声明为接口描述的成员提供实现,因此,清单 9.9 中的表达式实现了成员 Check 和 Report。

整个对象表达式就是正常的 F# 表达式定义:只做一件事,返回值。如果我们看到从 F# Interactive 的输出[3],可以发现,它返回ClientTest 类型的值。这是接口类型,因此,对象表达式返回实现接口的具体值,就像 lambda 函数返回实现了抽象函数类型的函数值一样。

 

注意

 

从技术上来说,F# 编译器创建实现接口的类,对象表达式返回这个类的新实例。然而,类的声明只是内部的,所以,不能直接访问这个类。我们唯一需要了解的是,它实现了指定的接口。

这是类似于 C# 3.0 中的匿名类型,编译器也在幕后创建了隐藏的类,我们不能直接访问。在 C# 中,我们知道类的属性是什么,但是,有关属性的信息仅在方法内部可用;相反,在 F# 中,我们知道类实现了哪一个接口,因此,我们可以使用它,而没有任何限制。我们可以写返回已知接口类型的方法,并使用对象表达式来实现。

 

在本节,我们学习了如何在 F# 面向行为的应用程序中,使用接口类型,完成迭代开发的最后一步。接口为我们提供了符合 .NET 习惯的解决方案,但 F# 提供的功能,能够以自然的方式使用接口,与函数风格保持一致。由于有了对象表达式,实现接口比构造函数记录更容易。

在本章的后面,我们将学习使用接口,使得从 C# 中调用 F# 代码成为可能。我们还没有讨论在 F# 中的类声明,因为,在纯 F# 项目中,普通的类不经常使用,但是,我们在 9.4 节简要地了解一下。在此之前,我们要学习在使用 .NET 库的常见的类型时,如何利用对象表达式。

 

9.2.2 使用接口对象类型