views:

576

answers:

6

I'm trying to get my head around SPL iterators and I've come up with 2 ways to handle it. I see the first version to be less complicated but the second version has composition feel to it (I think).

What am I not seeing is which one is preferable over the other? Or am I just over complicating this?

Here are my thoughts:

The object implements an iterator:

class BoxOfChocolates implements Iterator {

  private $id
  private $name; // e.g. Valentine's Heart Box
  private $maker; // e.g. Hersheys
  private $items; // array

  public function getChocolates() {
    $query = ...
    foreach($rows as $row) {
      $this->_items[] = new Chocolate() ...
    }
  }

  // ... necessary iterator implementation stuff


}

The object contains an iterate-able object:

class BoxOfChocolates {

  private $id;
  private $name;
  private $maker;
  private $items; // chocolates object

  public function getChocolates() {
    $this->items = new Chocolates();
    $this->items->getChocolates();
  }

}


class Chocolates implements Iterator {

  private $items;

  public function getChocolates() {
    $query = ...
    foreach($rows as $row) {
      $this->_items[] = new Chocolate() ...
    }
  }

  // ... necessary iterator implementation stuff

}
+2  A: 

It depends on the situation. Is the box of chocolates going t0 contain multiple collections? If so you need to have the collections be members.

Think of it this way. Is a box of chocolates a collection (i.e. PersonList) or something that owns a collection (i.e. a car may own a collection of it's last owners).

I think this falls under the first group

:)

nlaq
+1  A: 

i'd suggest the first group too, assuming your chocolate metaphor is pretty accurate to the actual class.

a box of chocolates really is your collection of chocolates, and as such it makes sense to want to iterate over the chocolates within that box. adding a separate chocolate list doesn't really add any value, and just seems to add an unnecessary layer.

Owen
A: 

Well, I don't know PHP, so I can't properly answer the question, but I'll try relating it to an area that I do know.

In C#, there are two interfaces: IEnumerable and IEnumerator. In your example, BoxOfChocolates would be Enumerable, Chocolates would be an Enumerator.

Enumerables merely have a method which returns an Enumerator. Enumerators have a concept of a current item, and a means of moving to the next item. In most cases, the Enumerator class isn't "seen". For example, in the line "foreach(Chocolate item in boxOfChoclates)" boxOfChocolates is a Enumerable; the Enumerator object is completely hidden by the foreach.

James Curran
A: 

Quite simply the container is the place to implement the iterator.

Richard Harrison
A: 

I think you should keep your collections separate from your iterators. I would agree with @James Curran that collections often have iterators -- in fact, they could have several. For instance, you may want an iterator that skips candy with nuts (though the typical case is to want one that reverses the ordering). In this case the meaning of the next() method changes. To handle that, implement iterators in separate classes that contain their own iteration semantics. Provide methods in the collection to obtain iterators of the proper sort. It's really an issue of separation of concerns. The collection doesn't care how the user iterates over it, that is the iterator's concern.

tvanfosson
+2  A: 

bbxbby, the best solution is usually the simplest one. You are definitely not overcomplicating things creating separate iterator object. In fact, PHP supports and encourages such aggregation providing IteratorAggregate interface. Objects implementing IteratorAggregate must contain getIterator() method returning Iterator. It is really what your getChocolated() method does. The nice thing about IteratorAggregate is that you can pass an object of it directly to foreach loop. Your code might look like like this one when using it:

class BoxOfChocolates implements IteratorAggregate

    private $chocolates = array();

    public function getIterator() {
        return new ArrayIterator(new ArrayObject($this->chocolates)));
    }

}

And then, somewhere in the code:

$box = new BoxOfChocolates();
foreach ($box as $chocolate) { ... }
Michał Rudnicki