《PHP设计模式介绍》第八章 迭代器模式(3)_PHP教程

编辑Tag赚U币
教程Tag:暂无Tag,欢迎添加,赚取U币!

推荐:《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 {
protected $key = 0;
protected $collection;
function __construct($collection) {
$this->collection = $collection;
}
function first() {
$this->key=0;
}
function next() {
return ( $this->key < $this->collection->count());
}
function isDone() {
return ($this->key >= $this->collection->count());
}
function currentItem() {
return $this->collection->get($this->key);
}
}

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

不同的迭代器 API

虽然前面的代码是 GoF 所述迭代器模式的完整实现,你还可能会发现四种方法的 API 有一点臃肿。如果是,你可以将 collapse next(), currentItem(), 和 isDone() 都并入 next() 中,用来从集合中返回本项或下一项,或者如果整个集合被遍历过了,则返回 false。这是一个测试不同 API 的代码:

class IteratorTestCase extends UnitTestCase {
// ...
function TestMediaIteratorUsage() {
$this->assertIsA(
$it = $this->lib->getIterator(‘media’)
,’LibraryIterator’);
$output = ‘’;
while ($item = $it->next()) {
$output .= $item->name;
}
$this->assertEqual(‘name1name2name3’, $output);
}
}

在上述代码中,注意简化的循环控制结构。 next() 返回对象或者false,允许你在 while 循环条件中执行分配。下面的一些示例检验使用较小接口的不同迭代器模式。为了方便,将 Library::getIterator() 方法更改为参数化的 Factory,以便你可以从单一方法中获取四种的方法迭代器或两种方法的迭代器(next() 和 reset())。

class Library {
// ...
function getIterator($type=false) {
switch (strtolower($type)) {
case ‘media’:
$iterator_class = ‘LibraryIterator’;
break;
default:
$iterator_class = ‘LibraryGofIterator’;
}
return new $iterator_class($this->collection);
}
}

这里面的 Library::getIterator() 现在接受一个参数以选择返回什么样的迭代器。缺省为 LibraryGofIterator(因此现有的测试仍然能够通过)。将字符串媒体传递给所创建的方法,并返回 LibraryIterator。这是一些实现 LibraryIterator 的代码:

class LibraryIterator {
protected $collection;
function __construct($collection) {
$this->collection = $collection;
}
function next() {
return next($this->collection);
}
}


请注意调试结果的红色标记!什么导致发生错误“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 {
// ...
function TestAvailableIteratorUsage() {
$this->lib->add($dvd = new Media(‘test’, 1999));
$this->lib->add(new Media(‘name4’, 1999));
$this->assertIsA(
$it = $this->lib->getIterator(‘available’)
,’LibraryAvailableIterator’);
$output = ‘’;
while ($item = $it->next()) {
$output .= $item->name;
}
$this->assertEqual(‘name1name2name3testname4’, $output);
$dvd->checkOut(‘Jason’);
$it = $this->lib->getIterator(‘available’);
$output = ‘’;
while ($item = $it->next()) {
$output .= $item->name;
}
$this->assertEqual(‘name1name2name3name4’, $output);
}
}

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


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教程/更新时间:2008-08-22
相关PHP教程