PHP设计模式之迭代器模式的深入解析
迭代器(iterator)模式,它在一个很常见的过程上提供了一个抽象:位于对象图不明部分的一组对象(或标量)集合上的迭代。迭代有几种不同的具体执行方法:在数组属性,集合对象,数组,甚至一个查询结果集之上迭代。
在对象的世界里,迭代器模式要维持类似数组的功能,看作是一个非侵入性对象刻面(facet),client类往往分离自真实对象实现,指iterator接口。只要有可能,我们可以给迭代器传送一个引用,代替将来可能发生变化的具体或抽象类。
参与者:
◆客户端(client):引用迭代器模式的方法在一组值或对象上执行一个循环。
◆迭代器(iterator):在迭代过程上的抽象,包括next(),isfinished(),current()等方法。
◆具体迭代器(concreteiterators):在一个特定的对象集,如数组,树,组合,集合等上实现迭代。
通过traversable接口,php原生态支持迭代器模式,这个接口由iterator和iteratoraggregate做了扩展,这两个子接口不仅是定义了一套标准的方法,每个traversable对象都可以原封不动地传递给foreach(),foreach是迭代器的主要客户端,iterator实现是真正的迭代器,而iteratoraggregate是有其它职责的traversable对象,它通过getiterator()方法返回一个iterator。
标准php库是php中绑定的唯一通用目的面向对象库,定义了额外的接口和公用类。outeriterator实现装饰一个iterator,cachingiterator和limititerator是这个接口的两个例子。
recursiveiterator是iterator接口为树形结构实现的一个扩展,它定义了一组额外的方法检查迭代中当前元素的子对象是否存在。recursivearrayiterator和recursivedirectoryiterator是这个接口的实现示例,这些类型的迭代器可以原样使用,或是用一个recursiveiteratoriterator桥接到一个普通的迭代器契约。这个outeriterator实现将会根据构造参数执行深度优先或广度优先遍历。
使用recursiveiteratoriterator时,可以将其传递给foreach,请看后面的代码示例,了解recursiveiterators的不同用法和它们的超集iterator。最后,seekableiterators向契约添加了一个seek()方法,它可以用于移动iterator的内部状态到一个特定的迭代点。
注意,迭代器是比对象集更好的抽象,因为我们可以让infiniteiterators,norewinditerators等,不用与普通数组阵列一致,因此,iterator缺少count()函数等功能。
在php官方手册中可以找到完整的spl迭代器列表。得益于对php的强力支持,使用迭代器模式的大部分工作都包括在标准实现中,下面的代码示例就利用了标准iterator和recursiveiterators的功能。
<?php
/**
* collection that wraps a numeric array.
* all five public methods are needed to implement
* the iterator interface.
*/
class collection implements iterator
{
private $_content;
private $_index = 0;
public function __construct(array $content)
{
$this->_content = $content;
}
public function rewind()
{
$this->_index = 0;
}
public function valid()
{
return isset($this->_content[$this->_index]);
}
public function current()
{
return $this->_content[$this->_index];
}
public function key()
{
return $this->_index;
}
public function next()
{
$this->_index++;
}
}
$array = array('a', 'b', 'c', 'd');
echo "collection: ";
foreach (new collection($array) as $key => $value) {
echo "$key => $value. ";
}
echo "\n";
/**
* usually iteratoraggregate is the interface to implement.
* it has only one method, which must return an iterator
* already defined as another class (e.g. arrayiterator)
* iterator gives a finer control over the algorithm,
* because all the hook points of iterator' contract
* are available for implementation.
*/
class numbersset implements iteratoraggregate
{
private $_content;
public function __construct(array $content)
{
$this->_content = $content;
}
public function contains($number)
{
return in_array($number, $this->_content);
}
/**
* only this method is necessary to implement iteratoraggregate.
* @return iterator
*/
public function getiterator()
{
return new arrayiterator($this->_content);
}
}
echo "numbersset: ";
foreach (new numbersset($array) as $key => $value) {
echo "$key => $value. ";
}
echo "\n";
// let's play with recursiveiterator implementations
$it = new recursivearrayiterator(array(
'a',
'b',
array(
'c',
'd'
),
array(
array(
'e',
'f'
),
array(
'g',
'h',
'i'
)
)
));
// $it is a recursiveiterator but also an iterator,
// so it loops normally over the four elements
// of the array.
echo "foreach over a recursiveiterator: ";
foreach ($it as $value) {
echo $value;
// but recursiveiterators specify additional
// methods to explore children nodes
$children = $it->haschildren() ? '{yes}' : '{no}';
echo $children, ' ';
}
echo "\n";
// we can bridge it to a different contract via
// a recursiveiteratoriterator, whose cryptic name
// should be read as 'an iterator that spans over
// a recursiveiterator'.
echo "foreach over a recursiveiteratoriterator: ";
foreach (new recursiveiteratoriterator($it) as $value) {
echo $value;
}
echo "\n";