首页 > 代码库 > 1.4.2 理解使用不可变性的代码

1.4.2 理解使用不可变性的代码

1.4.2 理解使用不可变性的代码

 

在前面介绍函数式风格的好处时,我们讨论过不可变性(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# 类的典型设计,我们在书中一直使用这个示例(有一些修改)。现在,我们已经知道了不可变类型的表象,有必要了解一下其影响。