隐藏实现是OOP的核心原则,并且在所有范例中都是一个好主意,但是对于支持延迟迭代的语言中的迭代器(或使用特定语言的任何称呼)而言,这尤其重要。
公开可迭代对象(甚至IList<T>
是类似接口)的具体类型的问题不在公开它们的对象中,而是在使用它们的方法中。例如,假设您有一个用于打印Foo
s 列表的函数:
void PrintFoos(IList<Foo> foos)
{
foreach (foo in foos)
{
Console.WriteLine(foo);
}
}
您只能使用该函数来打印foo列表-但前提是它们实现了 IList<Foo>
IList<Foo> foos = //.....
PrintFoos(foos);
但是,如果您要打印列表中每个偶数索引的项目怎么办?您需要创建一个新列表:
IList<Foo> everySecondFoo = new List<T>();
bool isIndexEven = true;
foreach (foo; foos)
{
if (isIndexEven)
{
everySecondFoo.Add(foo);
}
isIndexEven = !isIndexEven;
}
PrintFoos(everySecondFoo);
这很长,但是使用LINQ,我们可以将它写成单层,实际上更易读:
PrintFoos(foos.Where((foo, i) => i % 2 == 0).ToList());
现在,您最后注意到了.ToList()
吗?这会将惰性查询转换为列表,因此我们可以将其传递给PrintFoos
。这需要分配第二个列表,并两次传递项目(第一个列表中的一个创建第二个列表,第二个列表中的另一个打印它)。另外,如果我们有这个:
void Print6Foos(IList<Foo> foos)
{
int counter = 0;
foreach (foo in foos)
{
Console.WriteLine(foo);
++ counter;
if (6 < counter)
{
return;
}
}
}
// ........
Print6Foos(foos.Where((foo, i) => i % 2 == 0).ToList());
如果foos
有成千上万的条目怎么办?我们将必须仔细阅读所有这些文档,并分配一个庞大的列表,仅打印其中的6张!
输入枚举数-迭代器模式的C#版本。与其让我们的函数接受列表,不如让我们接受Enumerable
:
void Print6Foos(Enumerable<Foo> foos)
{
// everything else stays the same
}
// ........
Print6Foos(foos.Where((foo, i) => i % 2 == 0));
现在Print6Foos
可以懒洋洋地遍历列表的前6个项目,而我们不需要触摸其余的项目。
这里不公开内部表示是关键。当Print6Foos
接受一个列表时,我们必须给它一个列表-一种支持随机访问的东西-因此我们必须分配一个列表,因为签名不能保证它只会遍历整个列表。通过隐藏实现,我们可以轻松地创建Enumerable
仅支持功能实际需要的更有效的对象。