15.2.2 介绍行为
行为很大程度上是独立于动画库的。它们表示的值随时间变化:它不可能与图形或绘图相关。行为可能模拟网络流量、潮汐运动、股票价格,机器人的感官输入值,比如温度、或者任何其他随时间变化的测量。
这意味着,我们可以首先实现行为,不限制我们自己的动画。以通常的函数方式,我们首先考虑如何要表示行为,然后,实现一些简单的行为。我们已经创建了一些行为,能够将它们应用于动画;在本章的最后,我们将使用组合,开发更复杂的行为。
我们已经把行为描述为一种复合类型;让我们具体给它一个名字:Behavior<'T>。它是泛型的,以便它可以表示任何随时间变化的测量:温度行为可能返回 float32,而web 应用程序中的广告可能会返回广告的 Uri,在给定时间显示。从用户的角度来看,对内部表示形式,并不感兴趣。我们的库将提供基本的函数,来创建行为,用户将使用这些函数构建行为。因为我们要实现更简单的行为,需要在较低级别的工作。
在 F# 中表示行为
一个可能表示要保存初始值和一些差异,指定值随时间的变化。如果我们有一个初始值 10 和"每秒的差异" 为 1,那么,15 秒后值将是 25。这并不是很灵活,只能表示有限种类的动画值。我们将使用更普遍表示:函数的返回值,时间作为参数。这可以表示任何种类基于时间的值。这里是 F# 的类型声明。
Listing 15.2 Representing behaviors using functions (F#)
type BehaviorContext =
{ Time : float32 }
type Behavior<'T> =
| BehaviorFunc of (BehaviorContext -> 'T)
BehaviorContext 类型表示的参数是随时间变化的值,Behavior 类型声明为一种选项的差别联合,它包含一个函数,计算行为的值。该行为最简单的表示可能是 float32 �C> 'T 类型的 F# 函数,在这种情况下,我们不必声明类型 Behavior<'T>。
提示
在这一章中,我们将时间保存在行为中,值为 float32 类型。原因是,我们使用的行为将主要用于图形,而 System.Drawing 命名空间就使用这种数值类型。也有其他选项。最有趣的是,我们可以使用度量单位,把时间保存为 float<s>,表示时间以秒计。度量单位将会使代码更不言自明(self-explanatory)。我们可以定义像素单位(px),位置表示为 float<px>。你可以尝试自己为代码加上单位,或者可以在本书的网站上,找到使用单位的版本。
命名重要的类型,通常是个好主意。清单 15.2 把此项准则进一步分成两个步骤,因此,在将来,使用和扩展这个行为会更容易。
■ 使用简单的记录类型来打包当前的时间。这可以在时间之外,添加新的信息,由行为使用。
■ 使用一种选项的差别联合打包这个函数。这命名了这个类型,可以隐藏类型的内部表示。
这样,我们的库用户可以看到 Behavior<int>,不需要知道内部,它只是一个函数。事实上,我们将使用一种选项的差别联合,也意味着我们可以使用模式匹配,来访问这个函数值。现在,我们有了 F# 的类型声明,让我们看看它在 C# 中的做法。
在 C# 中表示行为
在 C# 中,除了创建类以外,虽然还有其他的可能,但是,可做的选择很少。我们可以直接使用委托表示行为,比如,Func<float, T>,但是,我们将按照 F# 的模式,隐藏内部表示。15.3 清单显示了一个简单实现,非常类似于 F# 代码。
Listing 15.3 Representing behaviors (C#)
internal struct BehaviorContext {
public BehaviorContext(float time) {
this.time = time;
}
private readonly float time;
public float Time { get { return time; } }
}
public class Behavior<T> {
internal Behavior(Func<BehaviorContext, T> f) {
this.f = f;
}
private readonly Func<BehaviorContext, T> f;
internal Func<BehaviorContext, T> BehaviorFunc
{ get { return f; } }
}
我们将使用简单的不可变的值类型,来保存当前时间,把它的值当作参数值,传递给不同的函数,所以,我们要确保它不可修改。在这种情况下,使类型不可变与创建只读字段和属性一样简单。行为本身的表示形式是一个泛型类,只有一个不可变属性,其类型为 Func<DrawingContext, T>,它对应于打包在 F# 联合中的函数值。
为了对用户隐藏 Behavior 类型的表示形式,把它标记为 internal 属性。同样,BehaviorContext 类型也是内部的。用户不直接构建行为,而是使用我们提供的基元函数,来创建。现在,让我们来看看这些基元,从 C# 版本开始。