Design Patterns: Iterator

In this blog post, I am going to explain one of the design patterns, an iterator. Wikipedia defines an iterator this way:  An iterator is an object that enables a programmer to traverse a container, particularly lists.

I explain concepts based on a book, “増補改訂版Java言語で学ぶデザインパターン入門”.

I translate code in Java in the book to code in Python. In this program, an iterator named BookShelfIterator is used to traverse books in a bookshelf. code can be found here

First, Aggregate interface is defined. This is an interface or an abstraction of aggregation of objects to which an iterator is applied.

# aggregate.py
from abc import ABC,abstractmethod

class Aggregate(ABC):
    @abstractmethod
    def iterator(self):
        pass

Aggregate class has an abstract method or an interface method that creates an iterator. Next, Iterator interface is defined. This interface plays a role of a loop variable that traverses objects in a container such as list.

# iterator.py
from abc import ABC, abstractmethod

class Iterator(ABC):
    @abstractmethod
    def has_next(self):
        pass

    @abstractmethod
    def next(self):
        pass

This interface has two methods. The first one, has_next, checks if there exists a next element. If that is the case, then it return True, it not, it returns False. The second one, next, returns a next element if exists.

Next, we define Book class which represents a book object which has a book name attribute and a method to get a book name.

# book.py
class Book:
    def __init__(self, name):
        self.name = name
    def get_name(self):
        return self.name

We then define BookShelf class which holds some books and has an iterator method that returns a new BookShelfIterator instance which is defined in the next paragraph. Note that this module has a cyclic relationship with bookshelf_iterator.py. In order to avoid an error from that relationship, import a module as a whole is used. This class implements Aggregate interface class or implements actual codes of an abstract class.

# bookshelf.py
from aggregate import Aggregate
from book import Book
import book_shelf_iterator as bsi


class BookShelf(Aggregate):
    def __init__(self, max_size):
        self.books = [0] * max_size
        self.last = 0

    def get_book_at(self, index):
        return self.books[index]

    def append_book(self, book):
        self.books[self.last] = book
        self.last += 1

    def get_length(self):
        return self.last

    def iterator(self):
        return bsi.BookShelfIterator(self)

Finally, let’s define BookShelfIterator class that scans through a bookshelf.

#bookshelf_iteraotr.py
from iterator import Iterator
import bookshelf


class BookShelfIterator(Iterator):
    def __init__(self, book_shelf):
        self.book_shelf = book_shelf
        self.index = 0

    def has_next(self):
        if (self.index < self.book_shelf.get_length()):
            return True
        else:
            return False

    def next(self):
        book = self.book_shelf.get_book_at(self.index)
        self.index += 1
        return book 

This class implements Iterator class and it has two methods. The first one is has_next whose return value is a boolean indicating whether there is a next element or not. The second method is next that increment an index and returns a book object.

Let’s run a program!!

# main.py
from bookshelf import BookShelf
from book import Book
from iterator import Iterator

def main():
    """
    Put 4 books in a bookshelf and print a book name iteratively. 
    """
    bookshelf = BookShelf(4)
    bookshelf.append_book(Book("Around the World in 80 Days"))
    bookshelf.append_book(Book("Bible"))
    bookshelf.append_book(Book("Cinderella"))
    bookshelf.append_book(Book("Daddy-Long-Legs"))
    it = bookshelf.iterator()
    assert isinstance(it, Iterator) == True
    while (it.has_next()):
        book = it.next()
        print(book.get_name())


if __name__ == "__main__":
    main()
# The script above produces the result below
Around the World in 80 Days
Bible
Cinderella
Daddy-Long-Legs

A benefit of an iterator. An iterator does not depend on the actual implementation of iterable or an object that can be iterated on. For example, while loop in main.py can be run as long as an iterable class, BookShelf in this example, has an iterator method returning an iterator which has both has_next and next methods.

Leave a comment