bbxbby
bbxbby

Reputation: 539

Should an object implement an iterator or contain another object that implements an iterator

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

}

Upvotes: 5

Views: 834

Answers (6)

Michał Niedźwiedzki
Michał Niedźwiedzki

Reputation: 12939

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) { ... }

Upvotes: 4

tvanfosson
tvanfosson

Reputation: 532435

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.

Upvotes: 0

Richard Harrison
Richard Harrison

Reputation: 19393

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

Upvotes: 0

James Curran
James Curran

Reputation: 103495

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.

Upvotes: 0

Owen
Owen

Reputation: 84493

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.

Upvotes: 1

user19302
user19302

Reputation:

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

:)

Upvotes: 2

Related Questions