所有的测试都通过了,PHP系列教程:设计模式介绍Ⅷ迭代器模式

  所有的测试都通过了,PHP系列教程:设计模式介绍Ⅷ迭代器模式
  

给定单独的对象来操作,你现在可以创建一个容器来包含他们:Library,

在上述代码中,注意简化的循环控制结构,

上面的测试用例看起来也很熟悉,因为其非常类似于前一个“发行版”迭代器,但是使用了 foreach 控制结构进行循环。
  PHP系列教程:设计模式介绍Ⅷ迭代器模式
  

上一节:《PHP设计模式介绍》第七章 策略模式
  

《PHP设计模式介绍》第八章 迭代器模式
  

类中的面向对象编程封装应用逻辑。类,就是实例化的对象,每个单独的对象都有一个特定的身份和状态。单独的对象是一种组织代码的有用方法,但通常你会处理一组对象或者集合。
  

属性来自 SQL 查询的一组数据就是一个集合,就像本书前面章节介绍的 Monopoly 游戏示例的对象列表。
  

集合不一定是均一的。图形用户界面框架中的 Window 对象可以收集任意数量的控制对象 - Menu、Slider 和 Button。并且,集合的实现可以有多种方式:PHP 数字是一个集合,但也是一个散列表,一个链接列表,一个堆栈以及队列。
  

如何操纵任意的对象集合?
  

使用迭代器模式来提供对集合内容的统一存龋
  $test = array;
$output = ‘’; reset;
do {
$= current;
} while );
echo $output; // produces ‘wothree’
  

你可能没有意识到这一点,但你每天都在使用迭代器模式 - 它潜藏在 PHP 的数组类型和各种数组操作函数中。。迭代器提供了不同固定迭代实现的统一接口,它完全包含了如何操纵特定集合的详细信息,包括显示哪些项及其显示顺序。

让我们创建一个简单的对象,在数组中对它进行操作。。对象 Lendable 表示诸如电影、相册等媒体,它作为 web 站点的一部分或服务,允许用户浏览或将他们的媒体集合分享给其他用户。让我们开始下面对 Lendable 基础设计的测试。
  // PHP5
class LendableTestCase extends UnitTestCase {
function TestCheckout {
$item = new Lendable;
$this->assertFalse;
$item->checkout;
$this->assertEqual;
$this->assertEqual;
}
function TestCheckin {
$item = new Lendable;
$item->checkout;
$item->checkin;
$this->assertEqual;
$this->assertFalse;
}
}
  

要实现这一最初测试的需求,我们来创建一个带有若干公共属性和一些方法的类,来触发这些属性的值:
  class Lendable {
public $status = ‘library’;
public $borrower = ‘’;
public function checkout {
$this->status = ‘borrowed’;
$this->borrower = $borrower;
}
public function checkin {
$this->status = ‘library’;
$this->borrower = ‘’;
}
}
  

Lendable 是一个好的,普通的开端。让我们将它扩展到诸如 DVD 或 CD 的磁道项。媒体扩展了 Lendable,并且磁道详细记录了特定媒体的详细信息,包括项目的名称,发布的年份以及项本身的类型:
  class Media extends Lendable {
public $name; public $type; public $year;
public function __construct {
$this->name = $name;
$this->type = $type;
$this->year = $year;
}
}
  

要使事情更加简单,媒体有三个公共的实例变量,Media::name,Media::year 和Media::type。构造函数采用了两个参数,将第一个存储在 $name 中,第二个存储在 $year 中。构造函数还允许可选的第三个参数来指定类型。
  

给定单独的对象来操作,你现在可以创建一个容器来包含他们:Library。类似于常用的库,Library 应该能够添加,删除和计算集合中的项。甚至,Library 还应该允许访问集合中的单一的项。
  

我们开始构建 Library 的测试用例。
  class LibraryTestCase extends UnitTestCase {
function TestCount {
$lib = new Library;
$this->assertEqual);
}
}
  class Library {
function count {
return 0;
}
}
  

继续将一些有趣的功能添加到测试中:
  class LibraryTestCase extends UnitTestCase {
function TestCount { /**/ }
function TestAdd {
$lib = new Library;
$lib->add;
$this->assertEqual);
}
}
  

实现 add 的简单方法是建立在 PHP 灵活数组函数的基础上:你可以将项添加到实例变量并使用 count 来返回集合众项的数量。
  class Library {
protected $collection = array;
function count {
return count;
}
function add {
$this->collection = $item;
}
}
  

Library 现在是一个集合,但它没有提供检索或操纵单一数组成员的方法。
  

我们回到本章的重点,迭代器设计模式的实现。下列 UML 类图显示了 GoF 迭代器模式与 Media 和 Library 类结合使用巩固示例的方法。
  

? 你的集合类必须提供 Factory来创建迭代器的实例。
? 迭代器类定义 first 转到集合开始的接口,
  

next 移到序列中的下一个项作为你的循环,currentItem 从集合检索当前的项作为你的循环, isDone 用于指出你在整个集合中循环结束的时间。
  

在“示例代码”部分,LibraryGofIterator 类是一个直接实现 GoF 迭代器设计模式的示例。
  

在 Library 内实现 GoF 迭代器模式的第一步是为新的具体迭代器写一个新的测试用例。因为每一种测试方法都将操纵包含 Media 实例的 Library,你可以清空 UnitTestCase::setUp 方法,从而在每种测试的已知状态下将变量填充到 Library 中。
  

首先,将 Library::getIterator 方法作为LibraryGofIterator 类的 一个 Factory 实例。
  class IteratorTestCase extends UnitTestCase {
protected $lib;
function setup {
$this->lib = new Library;
$this->lib->add);
$this->lib->add);
$this->lib->add);
}

function TestGetGofIterator {
$this->assertIsA
,’LibraryGofIterator’);
}
}
  class Library {
//
function getIterator {
return new LibraryGofIterator;
}
}
  

getIterator 方法将 Library 的 $collection 传递给新的具体迭代器结构。这一方法有两个重要的实现:每个迭代器都是独立的,因此可以同时操作多个迭代器。另外,迭代器在数组上的操作是当迭代器被请求时才执行的。如果之后将另一个项添加到集合中,你必须请求另一个迭代器来显示它。让我们通过将声明添加到 TestGetGofIterator 方法以匹配迭代器设计模式,继续对测试进行加强。

如果你已经对整个集合进行遍历,则 isDone 方法只应该为 true。如果 iterator 刚刚创建,则 isDone 显然返回 false,从而指出集合可以遍历。
  class IteratorTestCase extends UnitTestCase {
function setup { /**/ }
function TestGetGofIterator {
$this->assertIsA
,’LibraryGofIterator’);
$this->assertFalse);
}
}
  

与 TDD 一样,尽可能实现最简单的代码来满足你的测试用例:
  class LibraryGofIterator {
function isDone {
return false;
}
}
  

因此,在第一个迭代器间,应该发生什么呢? currentItem 应该返回第一个 Media 对象,这个对象是在 IteratorTestCase::setUp 方法中添加的,isDone 应该继续为 false,因为另两个项仍然等待遍历。
  class IteratorTestCase extends UnitTestCase {
function setup { /**/ }
function TestGetGofIterator {
$this->assertIsA
,’LibraryGofIterator’);
$this->assertFalse);
$this->assertIsA, ‘Media’);
$this->assertEqual;
$this->assertFalse);
}
}
  

LibraryGofIterator 接收了构造函数中的 $collection, 这一点非常重要并从 currentItem 方法返回 current 项。
  class LibraryGofIterator {
protected $collection;
function __construct {
$this->collection = $collection;
}
function currentItem {
return current;
}
function isDone {
return false;
}
}
  

在下一个迭代会出现什么? next 方法应该更改currentItem 方法返回的项。下面的测试捕获了所期望的行为:
  class IteratorTestCase extends UnitTestCase {
function setup { /**/ }
function TestGetGofIterator {
$this->assertIsA, ‘LibraryGofIterator’);
$this->assertFalse);
$this->assertIsA, ‘Media’);
$this->assertEqual;
$this->assertFalse);
$this->assertTrue);
$this->assertIsA, ‘Media’);
$this->assertEqual;
$this->assertFalse);
}
}
  

重新建立在 PHP 的数组函数之上,在数组上使用 next:
  class LibraryGofIterator {
protected $collection;
function __construct {
$this->collection = $collection;
}
function currentItem {
return current;
}
function next {
return next;
}
function isDone {
return false;
}
}
  

除了 isDone 方法必须返回 之外,第三个迭代看起来很像其他的迭代。你还希望 next 能够成功移到下一个迭代:
  class IteratorTestCase extends UnitTestCase {
function setup { /**/ }
function TestGetGofIterator {
$this->assertIsA, ‘LibraryGofIterator’);
$this->assertFalse);
$this->assertIsA, ‘Media’);
$this->assertEqual;
$this->assertFalse);
$this->assertTrue);
$this->assertIsA, ‘Media’);
$this->assertEqual;
$this->assertFalse);
$this->assertTrue);
$this->assertIsA, ‘Media’);
$this->assertEqual;
$this->assertFalse);
$this->assertTrue);
}
}
  

对 next 和 isDone 方法稍加修改,所有的测试都通过了。代码如下:
  class LibraryGofIterator {
protected $collection;
function __construct {
$this->collection = $collection;
}
function first {
reset;
}
function next {
return );
}
function isDone {
return );
}
function currentItem {
return current;
}
}
  

迭代器测试用例只存在一个问题:它没有反映迭代器的典型用法。是的,它测试了迭代器模式的所有功能,但应用程序需要采用更简单的方法来使用迭代器。因此,下一步是使用更贴实际的代码来编写测试。
  class IteratorTestCase extends UnitTestCase {
protected $lib;
function setup { /**/ }
function TestGetGofIterator { /**/ }
function TestGofIteratorUsage {
$output = ‘’;
for ; !$it->isDone; $it->next){
$= $it->currentItem->name;
}
$this->assertEqual;
}
}
  

目前,迭代器的实现复制了某个数组,并使用 PHP 的内部指针来跟踪迭代。你还可以通过自己跟踪集合索引来实现迭代器。这需要 Library 中的一种新的 essor 方法来通过关键字访问对象。
  class Library {
//
function get {
if ) {
return $this->collection;
}
}
}
  

同样,在 Library::getIterator 方法中,你可能将 $this传递给构造程序,而不是将 $this 传递给集合。外部的迭代器然后只是内部地跟踪指针以了解它当前引用的是哪一个 Library 集合元素,并将使用构造行数中从引用到 Library 的传递来检索当前的对象。
  class LibraryGofExternalIterator {
protected $key = 0;
protected $collection;
function __construct {
$this->collection = $collection;
}
function first {
$this->key=0;
}
function next {
return );
}
function isDone {
return );
}
function currentItem {
return $this->collection->get;
}
}
  

这一实现假设你的集合数组从 0 开始建立索引,并且是完全连续的。
  

不同的迭代器 API
  

虽然前面的代码是 GoF 所述迭代器模式的完整实现,你还可能会发现四种方法的 API 有一点臃肿。如果是,你可以将 collapse next, currentItem, 和 isDone 都并入 next 中,用来从集合中返回本项或下一项,或者如果整个集合被遍历过了,则返回 false。这是一个测试不同 API 的代码:
  class IteratorTestCase extends UnitTestCase {
//
function TestMediaIteratorUsage {
$this->assertIsA
,’LibraryIterator’);
$output = ‘’;
while ) {
$= $item->name;
}
$this->assertEqual;
}
}
  

在上述代码中,注意简化的循环控制结构。 next 返回对象或者false,允许你在 while 循环条件中执行分配。下面的一些示例检验使用较小接口的不同迭代器模式。为了方便,将 Library::getIterator 方法更改为参数化的 Factory,以便你可以从单一方法中获取四种的方法迭代器或两种方法的迭代器。
  class Library {
//
function getIterator {
switch ) {
case ‘media’:
$iterator_class = ‘LibraryIterator’;
break;
default:
$iterator_class = ‘LibraryGofIterator’;
}
return new $iterator_class;
}
}
  

这里面的 Library::getIterator 现在接受一个参数以选择返回什么样的迭代器。缺省为 LibraryGofIterator。将字符串媒体传递给所创建的方法,并返回 LibraryIterator。这是一些实现 LibraryIterator 的代码:
  class LibraryIterator {
protected $collection;
function __construct {
$this->collection = $collection;
}
function next {
return next;
}
}
  

请注意调试结果的红色标记!什么导致发生错误“Equal expectation fails at character 4 with name1name2name3 and name2name3”?不知何故,跳过了第一次迭代 - 这是 bug。要修订该错误,对于 next 方法的第一次调用,返回 current。
  class LibraryIterator {
protected $collection;
protected $first=true;
function __construct {
$this->collection = $collection;
}
function next {
if {
$this->first = false;
return current;
}
return next;
}
}
  

Presto! 绿色条和改进的 while 循环迭代器。
  

利用迭代器,你不仅仅可以显示集合中的每一项。你还可以选择显示的项。修改 Library::getIterator 来使用其它两种迭代器类型。
  class Library {
//
function getIterator {
switch ) {
case ‘media’:
$iterator_class = ‘LibraryIterator’;
break;
case ‘available’:
$iterator_class = ‘LibraryAvailableIterator’;
break;
case ‘released’:
$iterator_class = ‘LibraryReleasedIterator’;
break;
default:
$iterator_class = ‘LibraryGofIterator’;
}
return new $iterator_class;
}
}
  

类 LibraryAvailableIterator 仅可以迭代状态为“library”的项”。
  class IteratorTestCase extends UnitTestCase {
//
function TestAvailableIteratorUsage {
$this->lib->add);
$this->lib->add);
$this->assertIsA
,’LibraryAvailableIterator’);
$output = ‘’;
while ) {
$= $item->name;
}
$this->assertEqual;
$dvd->checkOut;
$it = $this->lib->getIterator;
$output = ‘’;
while ) {
$= $item->name;
}
$this->assertEqual;
}
}
  

该测试创建一个新的介质实例,并将其存储在变量 $dvd 中。突出显示第一个 assertEqual 声明验证利用 LibraryAvailableIterator 进行迭代时,存在一个新的项。接下来,测试使用 checkOut 方法,并验证新的项已丢失,不显示。实现过滤得代码非常类似于 LibraryIterator::next,差别在于在返回项之前执行过滤。如果当前项与过滤条件不匹配,则代码返回 $this->next。
  class LibraryAvailableIterator {
protected $collection = array;
protected $first=true;
function __construct {
$this->collection = $collection;
}
function next {
if {
$this->first = false;
$ret = current;
} else {
$ret = next;
}
if {
return $this->next;
}
return $ret;
}
}
  

迭代器不仅可以显示全部或部分集合。而且,还可以按特定顺序显示集合。下面,创建一个按集合众介质的发布日期进行排序的迭代器。为了进行测试,请添加某些日期在 setUp 方法中添加的项之后的介质实例。如果迭代器运行,则这些日期较后的项应该位于迭代操作的最前面。
  class IteratorTestCase extends UnitTestCase {
//
function TestReleasedIteratorUsage {
$this->lib->add);
$this->lib->add);
$this->assertIsA
,’LibraryReleasedIterator’);
$output = array;
while ) {
$output = $item->’-’. $item->year;
}
$this->assertEqual);
}
}
  

该测试使用的项在每个迭代中略有不同:并不仅仅是在字符串值后添加 $name,而是,字符串同时具有 $name 和 $year 属性,这些属性随后将被添加到 $output 数组。LibraryReleasedIterator 的实现与 LibraryIterator 非常类似,除了 constuctor 中的一行语句:
  class LibraryReleasedIterator extends LibraryIterator {
function __construct {
usort;’));
$this->collection = $collection;
}
}
  

用粗体表示的这一行将 $collection 数组排在迭代之前。你可以通过简单地继承 LibraryIterator 类,来避免复制该类的其它所有代码。可以使用外部迭代器来实现相同的排序迭代吗?是的,但是你必须注意完成它的诀窍。
  class LibraryReleasedExternalIterator {
protected $collection;
protected $sorted_keys;
protected $key=-1;
function __construct {
$this->collection = $collection;
$sort_funct = create_function {
$collection = $c;
return;
}
return ->year –
$collection->get->year);’);
$sort_funct;
$this->sorted_keys = $this->collection->keys;
usort;
}
function next {
if ) {
return false;
} else {
return $this->collection->get;
}
}
}
  

其中,关键是创建用于排序的实用程序函数。排序函数必须能够访问集合,以便可以获取对照成员。然而,因为 gener- ated 函数在 usort 中使用,没有将集合作为其它参数传递的选项。相反,你可以利用上述代码块中显示的诀窍,在利用 usort 调用函数之前,将引用存储在函数中内部的集合中。排序的项是集合的关键字列表。当 usort 完成时,关键字会按照集合中每个对象的 year 属性的顺序进行排序。在 next 方法中,可以通过 get 方法访问集合中的对象,而不是间接通过 $sorted_keys 映射。如果重新调用外部版本的 GoF 风格的迭代器,则不连续的数组或关键字中的字符串可能会有问题。可以使用针对 sim- ple 外部迭代器的相同诀窍,来减少关键字顺序不连贯的问题。
  

《迭代器设计模式和 PHP》中必须论述“标准 PHP 库”迭代器。虽然,使用 while 循环结构可以非常紧凑,并且也很有用,但是 PHP 代码或许更适合数组迭代的 foreach 结构。直接在 foreach 循环中使用集合合适吗?这其实就是 SPL 迭代器的目标。
  

Fuecks 写过一篇文章,详细地介绍了 SPL 和 SPL 迭代器;请参阅。使用 SPL 是一种完全不同的实现迭代的方法,因此首先介绍一个新单元测试例子和一个新的类 ForeachableLibrary。
  class SplIteratorTestCase extends UnitTestCase {
protected $lib;
function setup {
$this->lib = new ForeachableLibrary;
$this->lib->add);
$this->lib->add);
$this->lib->add);
}
function TestForeach {
$output = ‘’;
foreach {
$= $item->name;
}
$this->assertEqual;
}
}
  

ForeachableLibrary 是实现 SPL 迭代器接口的集合。你必须执行 5 个函数来创建 SPL 迭代器:current、next、key、valid 和 rewind。 key 返回集合的当前索引。 rewind 类似于 reset:在集合启动时重新启动迭代。
  class ForeachableLibrary
extends Library implements Iterator {
protected $valid;
function current {
return current;
}
function next {
$this->valid = );
}
function key {
return key;
}
function valid {
return $this->valid;
}
function rewind {
$this->valid = );
}
}
  

这里,该代码仅仅实现了处理 $collection 属性的必需的函数。测试尚不成熟,因此,什么都有可能发生。存在一个问题:事实受限于一种迭代类型 - 排序,或者 fil- tering 不可用。可以采取措施来调整这种情况?是的!应用从策略模式中学到的知识,将 SPL 迭代器的 5 个函数作为另一个对象的示例。这是关于 PolymorphicForeachableLibrary 的测试。
  class PolySplIteratorTestCase extends UnitTestCase {
protected $lib;
function setup {
$this->lib = new PolymorphicForeachableLibrary;
$this->lib->add);
$this->lib->add);
$this->lib->add);
}
function TestForeach {
$output = ‘’;
foreach {
$= $item->name;
}
$this->assertEqual;
}
}
  

这种情况与 SplIteratorTestCase 测试的唯一差别在于 $this->lib 属性类是在 setUp 方法中创建的。这意味着:这两个类的运行方式必须一致。PolymorphicForeachableLibrary:class PolymorphicForeachableLibrary扩展库
  implements Iterator {
protected $iterator;
function current {
return $this->iterator->current;
}
function next {
return $this->iterator->next;
}
function key {
return $this->iterator->key;
}
function valid {
return $this->iterator->valid;
}
function rewind {
$this->iterator =
new StandardLibraryIterator;
$this->iterator->rewind;
}
}
  

扩展库加入集合处理方法。并添加 SPL 方法,这些方法代表了 $iterator 属性,在 rewind 中创建。以下是StandardLibraryIterator 的代码。
  class StandardLibraryIterator {
protected $valid;
protected $collection;
function __construct {
$this->collection = $collection;
}
function current {
return current;
}
function next {
$this->valid = );
}
function key {
return key;
}
function valid {
return $this->valid;
}
function rewind {
$this->valid = );
}
}
  

该代码看起来很熟悉:实际上,这来自于 5 个 SPL 函数ForeachableLibrary 类。
  

现在,代码更加复杂了,但是其如何支持其它迭代器类型?添加一个关于“发行版”迭代器的测试,来查看这种设计的其它迭代器如何工作。
  class PolySplIteratorTestCase extends UnitTestCase {
//
function TestReleasedForeach {
$this->lib->add);
$this->lib->add);
$output = array;
$this->lib->iteratorType;
foreach {
$output = $item->’-’. $item->year;
}
$this->assertEqual);
}
}
  

上面的测试用例看起来也很熟悉,因为其非常类似于前一个“发行版”迭代器,但是使用了 foreach 控制结构进行循环。
  class PolymorphicForeachableLibrary
extends Library implements Iterator {
protected $iterator_type;
protected $iterator;
function __construct {
$this->iteratorType;
}
function iteratorType {
switch) {
case ‘released’:
$this->iterator_type = ‘ReleasedLibraryIterator’;
break;
default:
$this->iterator_type = ‘StandardLibraryIterator’;
}
$this->rewind;
}
//
function rewind {
$type = $this->iterator_type;
$this->iterator = new $type;
$this->iterator->rewind;
}
}
  

新的 iteratorType 方法使你转变要使用的迭代器的类型。
  class ReleasedLibraryIterator
extends StandardLibraryIterator {
function __construct {
usort;’));
$this->collection = $collection;
}
}
  

你可以简单地通过扩展 StandardLibraryIterator 并覆盖构造函数来添加入局数组的排序,从而实现 ReleasedLibraryIterator。并且,通过它,你可以有一个 working PolymorphicForeachableLibrary。

总结

迭代器是标准化地地处理应用程序中对象集合的方法。这些例子是基于数组的,但是对于拥有同一个接口的非数组集合,工作起来将更加强大。使用 foreach 控制结构方式的集合确实非常酷。 SPL 实现中最不幸的问题是与迭代器可能存在的名称空间冲突。有多少 PHP4 面向对象的代码拥有类似于迭代器类作为库迭代器类的基类?在一些容量中有多少 5 种必需方法的定义?可能一个更加具有深刻含义的名称就能实现 Foreachable。如果你选择使用 SPL,则还应该研究其它支持的迭代器,例如RecursiveArrayIterator 和其它众多迭代器。
  

下一节:《PHP设计模式介绍》第九章 观测模式
  

类中的面向对象编程封装应用逻辑,class IteratorTestCase extends UnitTestCase {
function setup { /**/ }
function TestGetGofIterator {
$this->assertIsA,而且,还可以按特定顺序显示集合,有多少 PHP4 面向对象的代码拥有类似于迭代器类作为库迭代器类的基类?在一些容量中有多少 5 种必需方法的定义?可能一个更加具有深刻含义的名称就能实现 Foreachable。