Iterator Design Pattern in Kotlin

1. Definition

The Iterator Design Pattern provides a way to access the elements of an aggregate object (like collections) sequentially without exposing its underlying representation. It allows for the creation of custom iteration logic tailored to specific collections.

2. Problem Statement

When you need to provide multiple ways to iterate over a collection or when the collection's underlying structure is complex, directly accessing elements becomes tricky. Exposing the inner workings of the collection might also risk data integrity and tight coupling.

3. Solution

Introduce an iterator object that encapsulates the logic required for iteration. This iterator knows how to access elements in the collection, one at a time, without the client needing to know the collection's internal structure.

4. Real-World Use Cases

1. Navigating through a playlist in a media player.

2. Browsing through pages in a book or a digital reader.

3. Iterating over different tree or graph structures.

5. Implementation Steps

1. Create an Iterator interface that defines methods like hasNext() and next().

2. Create a concrete iterator for each collection class.

3. The collection class should have a method to return its iterator.

6. Implementation in Kotlin Programming

// Step 1: Define the Iterator interface
interface Iterator<T> {
    fun hasNext(): Boolean
    fun next(): T?
}
// Step 2: Concrete Iterator
class BookShelfIterator(private val bookShelf: BookShelf) : Iterator<Book> {
    private var index = 0
    override fun hasNext(): Boolean {
        return index < bookShelf.getLength()
    }
    override fun next(): Book? {
        return bookShelf.getBookAt(index++)
    }
}
// Aggregate class
class BookShelf : Iterable<Book> {
    private val books = mutableListOf<Book>()
    fun addBook(book: Book) {
        books.add(book)
    }
    fun getLength() = books.size
    fun getBookAt(index: Int) = books[index]
    override fun iterator(): Iterator<Book> = BookShelfIterator(this)
}
data class Book(val name: String)
// Client Code
fun main() {
    val bookShelf = BookShelf()
    bookShelf.addBook(Book("Design Patterns"))
    bookShelf.addBook(Book("Effective Java"))
    bookShelf.addBook(Book("Clean Code"))
    val iterator = bookShelf.iterator()
    while (iterator.hasNext()) {
        val book = iterator.next()
        println(book?.name)
    }
}

Output:

Design Patterns
Effective Java
Clean Code

Explanation:

1. We defined an Iterator interface with essential methods: hasNext() and next().

2. BookShelfIterator is a concrete implementation of this interface, tailored to work with the BookShelf class.

3. The BookShelf class, representing our collection, implements the Iterable interface and returns its custom iterator.

4. The client code demonstrates how to iterate over the BookShelf collection using the BookShelfIterator.

7. When to use?

Use the Iterator pattern when:

1. You want to provide a standardized way to traverse different collections.

2. The internal structure of your collection is complex, and you want to hide the complexity from clients.

3. You need to provide multiple styles of iteration over the same collection.

Comments