class IteratorTestCase extends UnitTestCase { protected $lib; function setup() { /* ... */ } function TestGetGofIterator() { /* ... */ } function TestGofIteratorUsage() { $output = ‘’; for ($it=$this->lib->getIterator(); !$it->isDone(); $it->next()){ $output .= $it->currentItem()->name; } $this->assertEqual(‘name1name2name3’, $output); } } |
目前,迭代器的实现复制了某个数组(集合),并使用 PHP 的内部指针来跟踪迭代。你还可以通过自己跟踪集合索引来实现迭代器。这需要 Library 中的一种新的 accessor 方法来通过关键字访问对象。
class Library { // ... function get($key) { if (array_key_exists($key, $this->collection)) { return $this->collection[$key]; } } } |
同样,在 Library::getIterator() 方法中,你可能将 $this(library 本身)传递给构造程序,而不是将 $this 传递给集合(数组包含Media 集合)。外部的迭代器然后只是内部地跟踪指针以了解它当前引用的是哪一个 Library 集合元素,并将使用构造行数中从引用到 Library 的传递来检索当前的对象。
class LibraryGofExternalIterator { |
不同的迭代器 API
虽然前面的代码是 GoF 所述迭代器模式的完整实现,你还可能会发现四种方法的 API 有一点臃肿。如果是,你可以将 collapse next(), currentItem(), 和 isDone() 都并入 next() 中,用来从集合中返回本项或下一项,或者如果整个集合被遍历过了,则返回 false。这是一个测试不同 API 的代码:
class IteratorTestCase extends UnitTestCase { |
class Library { |
class LibraryIterator { |
class LibraryIterator { protected $collection; protected $first=true; function __construct($collection) { $this->collection = $collection; } function next() { if ($this->first) { $this->first = false; return current($this->collection); } return next($this->collection); } } |
过滤迭代器
利用迭代器,你不仅仅可以显示集合中的每一项。你还可以选择显示的项。修改 Library::getIterator() 来使用其它两种迭代器类型。
class Library { // ... function getIterator($type=false) { switch (strtolower($type)) { 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($this->collection); } } |
类 LibraryAvailableIterator 仅可以迭代状态为“library”的项”(重新调用 checkOut() 方法,将状态更改为“borrowed”)。
class IteratorTestCase extends UnitTestCase { |
class LibraryAvailableIterator { protected $collection = array(); protected $first=true; function __construct($collection) { $this->collection = $collection; } function next() { if ($this->first) { $this->first = false; $ret = current($this->collection); } else { $ret = next($this->collection); } if ($ret && ‘library’ != $ret->status) { return $this->next(); } return $ret; } } |
迭代器不仅可以显示全部或部分集合。而且,还可以按特定顺序显示集合。下面,创建一个按集合众介质的发布日期进行排序的迭代器。为了进行测试,请添加某些日期在 setUp() 方法中添加的项之后的介质实例。如果迭代器运行,则这些日期较后的项应该位于迭代操作的最前面。
class IteratorTestCase extends UnitTestCase { // ... function TestReleasedIteratorUsage() { $this->lib->add(new Media(‘second’, 1999)); $this->lib->add(new Media(‘first’, 1989)); $this->assertIsA( $it = $this->lib->getIterator(‘released’) ,’LibraryReleasedIterator’); $output = array(); while ($item = $it->next()) { $output[] = $item->name .’-’. $item->year; } $this->assertEqual( ‘first-1989 second-1999 name1-2000 name3-2001 name2-2002’ ,implode(‘ ‘,$output)); } } |
该测试使用的项在每个迭代中略有不同:并不仅仅是在字符串值后添加 $name,而是,字符串同时具有 $name 和 $year 属性,这些属性随后将被添加到 $output 数组。LibraryReleasedIterator 的实现与 LibraryIterator 非常类似,除了 constuctor 中的一行语句:
class LibraryReleasedIterator extends LibraryIterator { function __construct($collection) { usort($collection, create_function(‘$a,$b’,’ return ($a->year - $b->year);’)); $this->collection = $collection; } } |
用粗体表示的这一行将 $collection 数组排在迭代之前。你可以通过简单地继承 LibraryIterator 类,来避免复制该类的其它所有代码。可以使用外部迭代器来实现相同的排序迭代吗?是的,但是你必须注意完成它的诀窍。
延伸阅读:
从魔兽看PHP设计模式
《PHP设计模式介绍》导言
PHP设计模式介绍 第一章 编程惯用法
PHP设计模式介绍 第二章 值对象模式
PHP设计模式介绍 第三章 工厂模式
PHP设计模式介绍 第四章 单件模式
PHP设计模式介绍 第五章 注册模式
PHP设计模式介绍 第六章 伪对象模式
PHP设计模式介绍 第七章 策略模式