首 页   · 站长博客 · 用户注册 · 会员登陆  · 会员排行  ·最新主题  ·最近回复  精华区  版权声明  ·论坛管理
  当前登录身份:游客,请先登录。  笔名: 口令: 验证码:   
楼 主  index »  PHP相关资源下载区 » 《PHP设计模式介绍》第八章 迭代器模式(2)  


  作者:lxshark
  注册时间:2009-08-06
  主题/回复:49/50
  积分:639
  等级:★★★(六级)
  称号:声名鹊起

  lxshark@yeah.net..
  593993745
  hi.baidu.com/mythicsky

 

 发表:2009-08-08 20:50:12 阅读 2185 次 回复 1 次 得分0  |   字号 字色
《PHP设计模式介绍》第八章 迭代器模式(2)
目前,迭代器的实现复制了某个数组(集合),并使用 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 类,来避免复制该类的其它所有代码。可以使用外部迭代器来实现相同的排序迭代吗?是的,但是你必须注意完成它的诀窍。



class LibraryReleasedExternalIterator {
protected $collection; protected $sorted_keys; protected $key=-1;
function __construct($collection) {
$this->collection = $collection;
$sort_funct = create_function(
í $a,$b,$c=false’,
í畇tatic $collection;
if ($c) {
$collection = $c;
return;
}
return ($collection->get($a)->year -
$collection->get($b)->year);’);
$sort_funct(null,null,$this->collection);
$this->sorted_keys = $this->collection->keys();
usort($this->sorted_keys, $sort_funct);
}
function next() {
if (++$this->key >= $this->collection->count()) {
return false;
} else {
return $this->collection->get($this->sorted_keys[$this->key]);
}
}
}
 

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



SPL 迭代器

     《迭代器设计模式和 PHP》中必须论述“标准 PHP 库”(SPL)迭代器。虽然,使用 while 循环结构可以非常紧凑,并且也很有用,但是 PHP 代码或许更适合数组迭代的 foreach 结构。直接在 foreach 循环中使用集合合适吗?这其实就是 SPL 迭代器的目标。(尽管本章整篇都是写 PHP5,下列 SPL 代码只能在 PHP5 中运行,并且仅当在 PHP5 编译中将 SPL 启用。)

Fuecks 写过一篇文章,详细地介绍了 SPL 和 SPL 迭代器;请参阅 http://www.site-


point.com/article/php5-standard-library。使用 SPL 是一种完全不同的实现迭代的方法,因此首先介绍一个新单元测试例子和一个新的类 ForeachableLibrary。

class SplIteratorTestCase extends UnitTestCase {
protected $lib;
function setup() {
$this->lib = new ForeachableLibrary;
$this->lib->add(new Media(‘name1’, 2000));
$this->lib->add(new Media(‘name2’, 2002));
$this->lib->add(new Media(‘name3’, 2001));
}
function TestForeach() {
$output = ‘’;
foreach($this->lib as $item) {
$output .= $item->name;
}
$this->assertEqual(‘name1name2name3’, $output);
}
}
 



ForeachableLibrary 是实现 SPL 迭代器接口的集合。你必须执行 5 个函数来创建 SPL 迭代器:current()、next()、key()、valid() 和 rewind()。 key() 返回集合的当前索引。 rewind() 类似于 reset():在集合启动时重新启动迭代。


class ForeachableLibrary
extends Library implements Iterator {
protected $valid;
function current() {
return current($this->collection);
}
function next() {
$this->valid = (false !== next($this->collection));
}
function key() {
return key($this->collection);
}
function valid() {
return $this->valid;
}
function rewind() {
$this->valid = (false !== reset($this->collection));
}




这里,该代码仅仅实现了处理 $collection 属性的必需的函数。(如果你没有实现所有 5 个函数,并且将实现迭代器添加到类 definition,则 PHP 将出现致命错误。)测试尚不成熟,因此,什么都有可能发生。存在一个问题:事实受限于一种迭代类型 - 排序,或者 fil- tering 不可用。可以采取措施来调整这种情况?是的!应用从策略模式中学到的知识(请参阅第 7 章),将 SPL 迭代器的 5 个函数作为另一个对象的示例。这是关于 PolymorphicForeachableLibrary 的测试。


class PolySplIteratorTestCase extends UnitTestCase {
protected $lib;
function setup() {
$this->lib = new PolymorphicForeachableLibrary;
$this->lib->add(new Media(‘name1’, 2000));
$this->lib->add(new Media(‘name2’, 2002));
$this->lib->add(new Media(‘name3’, 2001));
}
function TestForeach() {
$output = ‘’;
foreach($this->lib as $item) {
$output .= $item->name;
}
$this->assertEqual(‘name1name2name3’, $output);
}




这种情况与 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->collection);
$this->iterator->rewind();
}
}
 
扩展库加入集合处理方法。并添加 SPL 方法,这些方法代表了 $iterator 属性,在 rewind() 中创建。以下是StandardLibraryIterator 的代码。



class StandardLibraryIterator {
protected $valid;
protected $collection;
function __construct($collection) {
$this->collection = $collection;
}
function current() {
return current($this->collection);
}
function next() {
$this->valid = (false !== next($this->collection));
}
function key() {
return key($this->collection);
}
function valid() { 
return $this->valid;
}
function rewind() {
$this->valid = (false !== reset($this->collection));
}
}
 
该代码看起来很熟悉:实际上,这来自于 5 个 SPL 函数ForeachableLibrary 类。


测试类

现在,代码更加复杂了,但是其如何支持其它迭代器类型?添加一个关于“发行版”迭代器的测试,来查看这种设计的其它迭代器如何工作。


class PolySplIteratorTestCase extends UnitTestCase {
// ...
function TestReleasedForeach() {
$this->lib->add(new Media(‘second’, 1999));
$this->lib->add(new Media(‘first’, 1989));
$output = array();
$this->lib->iteratorType(‘Released’);
foreach($this->lib as $item) {
$output[] = $item->name .’-’. $item->year;
}
$this->assertEqual(
‘first-1989 second-1999 name1-2000 name3-2001 name2-2002’
,implode(‘ ‘,$output));
}
}
 
上面的测试用例看起来也很熟悉,因为其非常类似于前一个“发行版”迭代器,但是


使用了 foreach 控制结构进行循环。


class PolymorphicForeachableLibrary
extends Library implements Iterator {
protected $iterator_type;
protected $iterator;
function __construct() {
$this->iteratorType();
}
function iteratorType($type=false) {
switch(strtolower($type)) {
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->collection);
$this->iterator->rewind();
}
}
 

新的 iteratorType() 方法使你转变要使用的迭代器的类型。(因为迭代器类型并不是在对象安装期间选中的,并且你可以在空闲时再次调用 iteratorType() 方法来选择不同迭代器类型,所以实际上是在 State 模式执行代码,而不是 Strategy 模式。)



class ReleasedLibraryIterator
extends StandardLibraryIterator {
function __construct($collection) {
usort($collection
,create_function(‘$a,$b’,’return ($a->year - $b->year);’));
$this->collection = $collection;
}




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

总结

迭代器是标准化地地处理应用程序中对象集合的方法。这些例子是基于数组的,但是对于拥有同一个接口的非数组集合,工作起来将更加强大。使用 foreach 控制结构方式的集合确实非常酷。 SPL 实现中最不幸的问题是与迭代器可能存在的名称空间冲突。有多少 PHP4 面向对象的代码拥有类似于迭代器类作为库迭代器类的基类?在一些容量中有多少 5 种必需方法的定义?可能一个更加具有深刻含义的名称就能实现 Foreachable。如果你选择使用 SPL,则还应该研究其它支持的迭代器,例如RecursiveArrayIterator 和其它众多迭代器。
 
 
 1#楼  
 
  回复人:lxshark
  注册时间:2009-08-06
  主题/回复:49/50
  积分:639
  等级:★★★(六级)
  称号:声名鹊起

   
 1#楼 发表于2009-08-08 21:34:54  评分:× 

回复给楼主(lxshark)
希望能对你们更好的学习PHP有所帮助,也期望大家有时间多光顾我的
点此打开链接:http://hi.baidu.com/mythicsky
  页数1/1首页 « 1 » 末页
  发表回复:您还没有登陆,无法发表回复。请先[登陆]

一起PHP技术联盟 主办:一起PHP 联系方式:站长QQ:4304410 QQ群:8423742 20159565 站长博客 E-mail: nqp@nqp.me 执行时间:0.022sec
SimsmaBBS 2008 (v6.0) Developed by 17php.com,Copyright(C)2003-2010 All rights reserved. 副本授权:一起PHP官方专用版