Design Patterns: Iterator Pattern

ClintClint
3 min read

The Iterator pattern is a behavioral design pattern that provides a way to access the elements of a collection object sequentially without exposing its underlying representation. This pattern is suitable for projects where there is a need to traverse or iterate over a collection of objects while keeping the code decoupled and independent of the specific type of collection.

One example of a project where the Iterator pattern is useful is the development of a music streaming service that allows users to browse and play their playlists. By using the Iterator pattern, a generic iterator interface can be defined that allows the music player to iterate over different types of playlists, such as albums, genres, or moods. This can help to improve the modularity and scalability of the music streaming service and make it easier to add new types of playlists or sources over time.

Another example of a project where the Iterator pattern is useful is the development of a financial analysis tool that reads data from different sources, such as CSV files, databases, or web APIs. By using the Iterator pattern, a generic iterator interface can be defined that allows the financial analysis tool to iterate over different types of data sources, while keeping the code decoupled and independent of the specific format or location of the data. This can help to improve the maintainability and extensibility of the financial analysis tool and make it easier to add new types of data sources or formats over time.

In general, the Iterator pattern should be used in projects where there is a need to traverse or iterate over a collection of objects while keeping the code decoupled and independent of the specific type of collection. It can help to improve the modularity and flexibility of the code, by providing a clear and consistent interface for iterating over different types of collections or data sources.

Let's get some hands-on practice by building a music player with the Iterator design pattern.

class MusicPlayer {
  constructor(songs) {
    this.songs = songs;
  }

  createIterator() {
    return new SongIterator(this.songs);
  }
}

class SongIterator {
  constructor(songs) {
    this.songs = songs;
    this.index = 0;
  }

  next() {
    if (this.hasNext()) {
      return this.songs[this.index++];
    }
    return null;
  }

  hasNext() {
    return this.index < this.songs.length;
  }
}

// usage example
const songs = ['song1', 'song2', 'song3', 'song4'];
const musicPlayer = new MusicPlayer(songs);
const iterator = musicPlayer.createIterator();

while (iterator.hasNext()) {
  const song = iterator.next();
  console.log(`Now playing: ${song}`);
}

In this example, we define a MusicPlayer class that takes a list of songs as its constructor argument. The MusicPlayer class provides a createIterator method that returns a new SongIterator object that can be used to traverse the list of songs.

The SongIterator class defines two methods: next and hasNext. The next method returns the next song in the list, and increments the internal index variable. The hasNext method returns true if there are more songs in the list to iterate over.

In the usage example, we create a new MusicPlayer object with a list of songs, and then create a new SongIterator object using the createIterator method. We then use a while loop to iterate over the list of songs using the next and hasNext methods of the SongIterator object.

This example demonstrates how the Iterator pattern can be used to provide a way to traverse a list of songs without exposing the underlying data structure.

0
Subscribe to my newsletter

Read articles from Clint directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Clint
Clint

Writer, software engineer, content creator, and all-around awesome guy.