首页 > 代码库 > 11.1.2.2 使用不可变数据结构

11.1.2.2 使用不可变数据结构

11.1.2.2 使用不可变数据结构

 

为了演示了以不可变风格,写相同的代码,但不一定必须使用函数式列表;即使使用标准的 List<T> 类型,我们一样能够避免修改集合。然而,不幸的是,要确保不意外修改列表,可能很难。

处理不允许修改的类型,有更好的方法。可以使用真正不可变类型,比如,第三章的FuncList<T>,或者 .NET Framework 中的 ReadOnlyCollection<T>;即使使用 IEnumerable<T>,一样可以得到良好的安全保障;可用于枚举任何集合类型的元素(包括可变的和不可变的),但不给提供任何直接的方式,修改底层集合;如果我们打算从其他线程修改集合,仍然可能有意想不到的结果,但并非这个例子中的情况。

我们使用IEnumerable <T> 实现同样的例子。LoadPlaces 和 PrintLongest方法变化不大,因此,这里就省略了;PrintMultiWord 方法更有意义:因为IEnumerable <T> 类型是不可变的,所以,前面的 RemoveAll策略,就不能再用了。此前,我们用这个方法从集合中删除所有单字地名,副作用使这个方法更难推断。如果我们想使用不可变类型,得到同样的结果,必须更加明确,如清单11.5 所示。

 

清单11.5 使用IEnumerable<T> 实现 PrintMultiWord (C#)

IEnumerable<string> PrintMultiWord(IEnumerable<string>names) {

  varnamesSpace = names.Where(s => s.Contains(" "));     [1]

  Console.WriteLine("Withspace: {0}", namesSpace.Count());

  returnnamesSpace;     [2]

}

 

处理不可变数据结时,是不能修改集合的,因此,该方法首先创建一个新的集合,只包含有多字的地名[1]。我们也实现了前面显式实现的副作用,所以,这个方法现在返回新的集合。当然,这不是真正的副作用,就是返回值。它实现了相同的结果,使调用者能够使用多字地名列表,如果他们想用的话[2]。

我们第一个例子是在所有的地名中找到最长的,第二个例子(输出“NewYork”)返回包含个空格的最长地名。清单11.6 是使用新的函数来实现这两个例子。

 

清单11.6 输出最长和多字最长地名(C#)

IEnumerable<string> places =

  LoadImmutablePlaces();

 

PrintMultiWord(places);

PrintLongest(places);     [1]

IEnumerable<string> places =

  LoadImmutablePlaces();

 

var placesSpace =

  PrintMultiWord(places);    [2]

PrintLongest(placesSpace);    [3]

 

现在,我们已经更明确地进行了修改,就不要对结果有所不同而感到奇怪了。在左侧的版本中,输出“Grantchester”[1],而选择包含空格的最长地名的版本,则输出“NewYork”。

清单11.6 还表明,使用不可变数据类型,可以让推断程序,并决定哪些重构是有效的更容易。在左侧的例子中,我们可以改变PrintMultiWord 和PrintLongest 的顺序,输出结果仍然相同(只是顺序相反);在清单11.6 的右侧,就不能改变调用的顺序,因为值placesSpace 是第一次调用的结果[2]。

因此,重构函数式代码时,可以更容易跟踪计算的依赖关系。我们可以看到,如果一个函数以其他调用的结果作为参数值,说明这个函数依赖于其他调用。因为,这在代码中是明确可见的,我们不可能犯意外重构的错误,由于错误修改的代码将不能通过编译,这在使用单元测试时也是非常有用的。

11.1.2.2 使用不可变数据结构