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 aUserIterator
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.
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 DETAILSWhat 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.