在前面介绍函数式风格的好处时,我们讨论过不可变性(immutability)。我们使用的示例是一个带边框的椭圆,但是代码的具体行为并不清楚。当我们用不可变对象重写了代码以后,它就变得更容易理解。在后面的章节中,我们会回到这个主题并更详细地讨论。此示例的目的是显示在实践中不可变的对象的表现。
再次强调,如果你在此时没能全部掌握,也不要担心。想象一下,我们正在编写一个游戏,其中的角色就是我们的目标,角色可以用类来表示,下面的清单是这个类的一部分:
Listing 1.7 Immutable representation of agame character (C#)
classGameCharacter
{
readonlyint health; |[1]
把所有字段声明为只读
readonly Point location; |
public GameCharacter(int
health, Pointlocation)
{
this.health = health;
|[2] 初始化不可变字段
this.location =location;
|
}
publicGameCharacter HitByShooting(Point target)
{
int newHealth =CalculateHealth(target);
|[3] 返回更新过健康值的游戏角色
returnnewGameCharacter(newHealth,
this.location); |
}
publicbool IsAlive
{
get {
return health > 0; }
}
// Other methods and properties omitted
}
在 C# 中,我们可以明确地把字段标记为不可变,用 readonly 关键字,这样,就不能改变字段的值;但是,如果该字段指向非不可变的类[ 即,引用可变类 ],目标对象仍然可以修改。要创建真正不可变的类,需要确保所有字段都被标记为 readonly,且字段的类型都是基本类型、不可变的值类型,或其它不可变的类。
根据这些条件,GameCharacter 类是不可变的,其所有的字段都用 readonly 限定符标记[1],int 是不可变的基本类型,Point 是不可变的值类型。当字段是只读时,值只能在创建对象时才可以设置,因此,我们只能在构造函数中设置角色的位置与健康值[2]。这样,对象初始化之后,就不能修改它的状态。那么,当操作需要修改游戏角色的状态时,我们该怎么做呢?
当你看到 HitByShooting 方法时,就知道答案了[3],它实现了游戏中对开枪的反应。它使用 CalculateHealth 方法(不在此示例中)来计算角色新的健康值。用命令式风格,这需要修改角色的状态,但这里是不可能的,因为类型是不可变的。相反,这个方法创建了GameCharacter 新的实例,表示修改后的角色,作为结果返回。
前面示例中的类,是不可变的 C# 类的典型设计,我们在书中一直使用这个示例(有一些修改)。现在,我们已经知道了不可变类型的表象,有必要了解一下其影响。
1.4.2 理解使用不可变性的代码