《PHP设计模式介绍》第八章 迭代器模式(3)_PHP教程
推荐:《PHP设计模式介绍》第七章 策略模式在编写面向对象的代码的时,有些时候你需要一个能够自己根据不同的条件来引入不同的操作对象实例。例如,一个菜单功能能够根据用户的“皮肤”首选项来决定是否采用水平的还是垂直的排
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 { |
请注意调试结果的红色标记!什么导致发生错误“Equal expectation fails at character 4 with name1name2name3 and name2name3”?不知何故,跳过了第一次迭代 - 这是 bug。要修订该错误,对于 next() 方法的第一次调用,返回 current()。
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); } } |
Presto! 绿色条和改进的 while 循环迭代器。
过滤迭代器
利用迭代器,你不仅仅可以显示集合中的每一项。你还可以选择显示的项。修改 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设计模式介绍》第八章 迭代器模式(3)。