The Iterator Pattern - A Variant Iterator API (
Page 4 of 4 )
While the foregoing code is a complete implementation of the Iterator pattern as described by GoF, you may find the four-method API a bit cumbersome. If so, you can collapse next(), currentItem(), and isDone()into justnext()
by having the latter either advance and return the current item from
the collection or return false if the entire collection has been
processed.
Here's one way to write a test for this variation of the 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);
}
}
In the code above, notice the simplified control structure for looping. next() returns an object or false, allowing you to perform the assignment inside the while loop conditional.
The next few examples explore variations of the Iterator pattern using the smaller interface. As a convenience, change the Library::getIterator() method to a parameterized Factory so you can get either the four-method iterator or the two-method iterator (next() and reset()) from that single method.
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);
}
}
Here, Library::getIterator() now accepts
a parameter to select what kind of iterator to return. The default is
LibraryGofIterator (so the existing tests still pass). Passing the
string media to the method creates and returns a LibraryIterator instead.
This is some code to implement LibraryIterator:
class LibraryIterator {
protected $collection;
function __construct($collection) {
$this->collection = $collection;
}
function next() {
return next($this->collection);
}
}
Oops! The dreaded red bar! What happened to get the error "Equal expectation fails at character 4 with name1name2name3 and name2name3"? Somehow, the first iteration was skipped—that's a bug. To fix the error, return current() for the first call of the next()method.
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! A green bar and a streamlined while loop iterator.