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.