Skip to main content

Real World Implementation Of The Iterator Pattern

A good real-world application of the Iterator pattern is traversing different types of collections. Here's an example with a Collection of Users ( stored in an array for this example) and a UserIterator to traverse it.

Real World Implemenation of The Iterator Pattern

This example assumes you have different types of collections (e.g. Array, LinkedList, etc) but for simplicity, we're focusing on the Array-based implementation. Here we define a User class, a UserCollection class that stores User objects, and a UserIterator that iterates over the UserCollection.

class User {
constructor(public name: string) {}
}

interface MyIteratorResult<T> {
value: T | null;
done: boolean;
}

interface MyIterator<T> {
next(): MyIteratorResult<T>;

hasNext(): boolean;
}

interface Collection<T> {
createIterator(): MyIterator<T>;
}

class UserCollection implements Collection<User> {
private users: User[] = [];

constructor(users: User[]) {
this.users = users;
}

createIterator(): MyIterator<User> {
return new UserIterator(this);
}

getItems(): User[] {
return this.users;
}
}

class UserIterator implements MyIterator<User> {
private collection: UserCollection;
private position: number = 0;

constructor(collection: UserCollection) {
this.collection = collection;
}

next(): MyIteratorResult<User> {
// Check if the iteration is complete
if (this.hasNext()) {
// If not, return the current item and increment the position
return {
value: this.collection.getItems()[this.position++],
done: false,
};
} else {
// If the iteration is complete, return an object with a null value and done set to true
return { value: null, done: true };
}
}

hasNext(): boolean {
return this.position < this.collection.getItems().length;
}
}

To use this, you can create a UserCollection, populate it with User instances, create an iterator and use it to traverse the collection.

// create some users
const users = [new User("Alice"), new User("Bob"), new User("Charlie")];

// create a UserCollection and populate it with users
const userCollection = new UserCollection(users);

// create an iterator
const iterator = userCollection.createIterator();

// use the iterator to traverse the collection
while (iterator.hasNext()) {
console.log(iterator.next().value?.name);
}

In this example, UserCollection is a concrete collection that stores User objects in an array. The UserIterator provides a way to iterate over the UserCollection. Clients don't need to know about the internal array in the UserCollection, they just need to know how to work with the UserIterator.

This allows you to change the internal implementation of UserCollection (for example, to use a different data structure instead of an array) without affecting any client code that uses the UserIterator.

Adavantages Of The Iterator Pattern

The Iterator pattern has several advantages:

Simplifies the Client Code

The Iterator pattern simplifies the client code by providing a common interface for traversing different types of collections. Clients can use this interface to traverse the collection without knowing its underlying representation.

In our example, the client code does not need to know how UserCollection is implemented. It only needs to obtain an iterator from it and use its next() and hasNext() methods to go through the collection.

const iterator = userCollection.createIterator();
while (iterator.hasNext()) {
console.log(iterator.next().value?.name);
}

Enables Different Types of Traversals

The Iterator pattern allows different types of traversals to be implemented. You can have forward iterators, backward iterators, or even random access iterators, depending on what your application needs.

Our example doesn't showcase this directly, but you can imagine modifying the UserIterator to traverse in reverse order or to jump forward multiple elements at a time.

Allows Concurrent Traversals

The Iterator pattern allows multiple traversals of the same collection to happen concurrently. Each iterator keeps its own state, so one iterator can be halfway through the collection while another is just starting, and they won't interfere with each other.

This is demonstrated by the fact that you can create multiple iterators from the same UserCollection and they will each keep track of their own position independently.

const iterator1 = userCollection.createIterator();
const iterator2 = userCollection.createIterator();

Encapsulation of Internal Structure

The Iterator pattern hides the internal details of the collection. This is useful when you want to provide a way for clients to traverse a collection without exposing its internal structure.

In our example, the client code doesn't need to know that UserCollection is backed by an array. All it knows is that it can get an iterator and use it to traverse the collection.

const iterator = userCollection.createIterator();

Uniform Interface

The Iterator pattern provides a uniform interface for traversing different collections. This makes it easier to write generic code that works with any collection.

This isn't fully demonstrated in our example, but if you had other types of collections (like BookCollection, MovieCollection, etc.), as long as they implemented the Collection interface, you could use the same code to traverse them.

TypeScript Course Instructor Image
TypeScript Course Instructor Image

Time To Transition From JavaScript To TypeScript

Level Up Your TypeScript And Object Oriented Programming Skills. The only complete TypeScript course on the marketplace you building TypeScript apps like a PRO.

SEE COURSE DETAILS

What Can You Do Next 🙏😊

If you liked the article, consider subscribing to Cloudaffle, my YouTube Channel, where I keep posting in-depth tutorials and all edutainment stuff for ssoftware developers.

YouTube @cloudaffle