我们前面提到过,以面向对象的方式理解函数,就是把它认为是有一个单独方法的接口;从清单 8.1 的代码中,我们可以看到IClientTest 就是这样声明的。这样,测试可以很容易地表示成简单的函数;在 C# 中,我们可以使用lambda 函数来写测试:
Func<Client, bool> isRiskyYearsInJob=
client => client.YearsInJob <2;
不使用接口类型,现在,我们使用 Func<Client, bool>,它表示一个函数,把 Client 作为参数值,并返回布尔值。以这种方式写代码,可以显著减少围绕表示测试的表达式的程式化代码的数量。
正如可以在集合中保存实现某种接口的对象,我们也可以创建保存函数值的集合,在清单 8.2 中,List<T>类型就实现了这个功能。注意,我们创建了完全标准的对象集合,可以遍历集合中的所有函数,或者通过添加或删除某些函数值,实现修改集合。
初始化集合时,我们可以很容易地写代码,在单独一个方法中,指定一组默认测试。我们可以使用 lambda 函数语法添加测试,而无需预先声明函数,也可以使用 C# 3.0 的功能,称为集合初始化器(collection initializer),使语法更简洁。
清单 8.2用函数列表检查是否应该贷款(C#)
class Client { [1] <-- 保存客户信息
public string Name { get; set;}
public int Income { get; set;}
public int YearsInJob { get; set;}
public bool UsesCreditCard { get;set; }
public bool CriminalRecord { get;set; }
}
static List<Func<Client, bool>>GetTests() { <-- 返回测试列表
return new List<Func<Client,bool>> { |
client =>client.CriminalRecord, |
client =>client.Income < 30000, | [2]集合初始化器,
client =>!client.UsesCreditCard, | 创建测试列表
client =>client.YearsInJob < 2 |
};
}
清单 8.2 使用了 C# 3.0 中的许多新功能,由于这些功能,我们写的代码与 F# 的实现非常类似。首先,我们使用自动属性,声明一个保存有关客户信息类[1];接下来,我们实现一个方法,返回测试的集合。方法的主体就是一个return 语句,创建 .NET 列表类型,使用集合初始化器,初始化元素[2]。这种方法在创建集合时指定值,同样的方法也适用数组。在这个背后,它调用了集合的 Add 方法,但是这样更清晰。
保存在集合中的值,是使用lambda 函数语法写的函数。注意,我们不必指定 client 参数的类型,因为 C# 编译器知道 Add 方法的参数,与泛型类型参数相同,在我们的例子中,是Func<Client, bool>。
注意
以行为为中心的程序,能从库中动态加载新的行为,是很正常的要求。对于我们的应用程序来说,能够写一个 .NET 类库,包含 GetTests方法,将返回测试列表,像早前的代码一样;我们的程序在执行时调用这个方法,得到测试,执行测试时无需知道更多相关内容。
这可以使用System.Reflection 命名空间下标准 .NET 类来实现。典型的情况是动态加载程序集,找到程序集内所有适当的类,在运行时创建它们的实例。在本书的网站上可以找到有关使用反射的详细信息。
我们已经有一个类来表示客户,一个测试集合,告诉我们是否向客户提供贷款,现在,我们看看如何运行测试。