5.3 可选值(Alternativevalues)
在前一节,我们讨论了如何创建能够将多个值组合成一个值。例如,有一个字符串值,和一个数字,我们能够创建一个复合值,包含字符串和数字。在本节,将看到如何构造既可能是字符串,也可能是数字的值。
首先,我们看一个示例,就能知道它应该在什么时候使用了。想象一下,我们要写一个安排任务和会议的应用程序,可能会有几种方法指定日期。对于只发生一次的事件,我们只想保存日期和时间;有些事件可能要重复发生,对于这类事件,需要保存开始发生的日期和时间,以及事件重复的时间间隔;我们还想支持没有指定时间的事件,称之为不定期事件。
这样,我们需要创建有三种不同选项的值,来描述对应的计划:无计划,一次执行,重复执行。在面向对象编程中,表示多种选项的典型方法,是使用类层次,有基类,其中包含了所有重要的抽象方法。在这里,我们还不知道会如何使用 Schedule 类型,但是,一个有用的抽象方法,它应该返回下一次计划发生的日期。图 5.1 显示了类层次结构,有一个抽象类 Schedule,和三个派生类对应三种选项。
图 5.1 表示三种不同类型计划的类层次结构,每种情况有不同的属性
使用这种表示方法,能够很容易在后期开发期间添加新的计划类型,但是,在这里,我们并不期望用这种常规做法;另一方面,很有可能需要为处理计划添加新的操作,例如,要获得上一次发生的时间,或者某个指定事件发生的序号,所有这些操作是不能通过使用 GetNextOccurrence 虚方法完成的。那么,就需要持续修改基类,但是,如果我们想把它加到共享库中,要使代码库更稳定,那又该如何呢?这个示例有点简单,在某些情况下,对于揭示整个类层次结构,还是有意义的。
在 C# 中的实现方法,既可以使用类型测试,也可以通过在基类中添加 Tag 属性使之更有效;它的类型可能是有三种可能值(Never、Once 和 Repeatedly)的枚举(比如称为 ScheduleType)。
这种方法使代码扩展性很差(通过添加新的计划类型),但是,写处理计划的方法很方便;在 C# 3.0 中,甚至可以写扩展方法来添加功能,用点表示法就能很好地访问。
如果你是面向对象编程的高手,可能发现这种思想有点不寻常,但是,一旦熟悉以后,就会承认它有更多有帮助的情况。另一方面,数据库设计中有一个类似的原则很常见,在数据库中保存计划时,不太可能添加新的计划类型,但是,一定能够添加新的存储过程来处理这些数据。我们很快将讨论面向对象和函数式设计的利与弊,但是,首先要讨论在 F# 中使用差别联合(discriminated unions)表示可选值。