首页 > 代码库 > 9.1.2 使用类型扩展追加成员

9.1.2 使用类型扩展追加成员

9.1.2 使用类型扩展追加成员 

 

在上一节我们提到过,可以为任何 F# 数据类型添加成员;现在,我们将使用差别联合来演示。这种种方法能够添加成员,而不需要修改任何原始代码。这样,我们将能够保留原始类型和原始的函数声明,不作修改,然后添加成员。

我们将扩展第五章声明 schedule 类型的示例,这个类型表示的事件可以只发生一次,或重复发生,或从不发生。除了数据类型之外,我们还创建了计算事件下一次发生时间的函数。清单 9.4 是代码稍作修改后的版本,我们使代码更紧凑,并使用简单的工具函数,重构了模式匹配中的 Once 分支;如果想比较的话,原始代码在清单 5.5 中。

 

清单 9.4 有函数的Schedule 数据类型 (F#)

type Schedule =   [1] <-- 声明原始类型

  | Never 

  | Once of DateTime 

  | Repeatedly of DateTime * TimeSpan

 

let futureOrMaxValue(dt) =   [2] <-- 实现工具函数

  if (dt > DateTime.Now) then dtelse DateTime.MaxValue

 

let getNextOccurrence(schedule) =   [3] <-- 指定公开的行为

  match schedule with 

  | Never ->DateTime.MaxValue 

  | Once(eventDate) ->futureOrMaxValue(eventDate) 

  | Repeatedly(startDate, interval)–> 

    let secondsFromFirst =(DateTime.Now - startDate).TotalSeconds 

    let q = max(secondsFromFirst / interval.TotalSeconds) 0.0 

   startDate.AddSeconds 

     (interval.TotalSeconds * (Math.Floor(q) + 1.0))

 

最重要的变化是,我们增加了工具函数 futureOrMaxValue[2]。这个改变并不显著提高可读性,只是用来说明有这种选择。在一个更复杂的项目中,肯定会有一些工具函数。

这个观点就是,在典型的 F# 源文件中,首先声明类型,然后,有一堆工具(私有)函数,再后是我们想要公开为成员的函数[3]。如果我们想利用上一节介绍的方法,把最后的函数变为成员,这是相当困难的。因为成员必须是类型声明的一部分,但是,我们通常想把工具函数放在类型和其成员之间!

要解决这个问题,需要使用固有类型扩展(intrinsic type extensions),它能够为在文件中先声明的类型,添加成员。清单 9.5 显示了我们如何能够为schedule 类型使用扩展。

 

清单 9.5 利用固有类型扩展添加成员 (F#)

type Schedule = 

  | Never 

  | Once of DateTime 

  | Repeatedly of DateTime *TimeSpan 

let futureOrMaxValue(dt) = 

  (...) 

let getNextOccurrence(schedule) =   [1]

  (...)

 

type Schedule with     [2]

  member x.GetNextOccurrence() =getNextOccurrence(x)     [3]

  member x.OccursNextWeek =                        | [4]

    getNextOccurrence(x)< DateTime.Now.AddDays(7.0)    |

 

对比清单 9.4 的代码,大部分没有改变,为了简洁,所以就省略了;唯一增加的是最后四行代码。第一行[2]定义了类型扩展,告诉 F# 编译器,要把后面的成员添加到指定名字的类型;后面就是正常的成员声明。因为我们已经把核心功能实现为函数[1],成员的实现就简单了[3]。除了得到下一次发生的时间以外,我们还增加了一个属性[4],使用私有函数检查下一次发生的时间是否就在接下来的一周。

如果学过 C# 3.0,就会发现类型扩展和扩展方法之间有相似性。使用类型扩展,可以为其他程序集中现有类型添加方法和属性。前一个清单的情况有所不同,因为我们使用了固有类型扩展。这是一种特殊的情况,即,声明原始类型和扩展在同一个文件中;在这种情况下,F# 编译器会把类型的两部分合并到同一个类中,我们也能访问在类型扩展中类型的私有成员。

清单 9.6 演示了调用清单 9.5 中的成员。使用类型扩展添加的成员,其行为与其他成员一样的,因此,清单没有任何意外惊喜。

 

清单 9.6 使用成员处理 Schedule  (F# Interactive)

> let sch = Repeatedly(DateTime.Now,TimeSpan(2, 0, 0, 0));; 

val sch : Schedule

 

> sch.OccursNextWeek();;   <-- 使用重复事件进行测试

val it : bool = true

 

> let sch = Never;; 

val sched : Schedule

 

> sch.OccursNextWeek();;   <-- 测试无计划事件的行为

val it : bool = false

 

正如我们处理记录时,通常的方法是创建 F# 值;对于在我们示例中的差别联合,就是使用 Repeatedly 或 Never 识别器(我们还可以使用 Once 识别器)。我们有了值以后,就可以使用面向对象的点符号调用它的成员。

正如我们刚看到的,在写成熟代码时,成员是非常有用的,因为它可以把代码包装成结构良好的片断,以方便使用类型。在 F# 的开发过程中,我们通常不首先写有成员的代码,只有代码测试通过以后,才添加成员,API 设计就固定了。我们已经讨论过添加成员的两种方法:

■类型足够简单时,直接在类型声明中追加成员。 

■对于复杂的类型,使用固有类型扩展,可以少改变代码。

类型扩展还有一个好处,可以在扩充之前,使用 F# Interactive 工具,测试类型和它的处理函数,因为我们不必一口气声明整个类型。

我们已经知道了,对于把以数据为中心的 F# 代码转变成实际的 .NET 应用程序或组件,成员是非常重要的。现在,我们将把注意力转向以行为中心的应用程序。

9.1.2 使用类型扩展追加成员