LLD - Design a Music Playlist Iterator - Iterator Design Pattern

ยท

3 min read

LLD - Design a Music Playlist Iterator - Iterator Design Pattern

Problem Statement

Design a music playlist iterator that allows traversing the playlist in different ways

  • in order (from start to end)

  • reverse (from end to start)

  • shuffled (in random order)

Understanding Iterator Design Pattern

This behavioral design pattern allows access to the elements of an aggregate object or collection without exposing underlying representations.

Blueprint of the Iterator Design Pattern
The Iterator design pattern involves the following key components:

  1. Iterator Interface: Defines the interface for accessing and traversing elements.

  2. Concrete Iterator: Implements the Iterator interface and keeps track of the current position in the traversal of the aggregate.

  3. Aggregate Interface: Defines the interface for creating an Iterator object.

  4. Concrete Aggregate: Implements the Aggregate interface and returns an instance of the Concrete Iterator.

Solution

Defining the data model

public class Song {
    private String title;
    private String artist;

    public Song(String title, String artist) {
        this.title = title;
        this.artist = artist;
    }

    public String getTitle() {
        return title;
    }

    public String getArtist() {
        return artist;
    }

    @Override
    public String toString() {
        return title + " by " + artist;
    }
}

Defining Iterator Interface
PlaylistIterator: Provides methods for traversing the playlist

public interface PlaylistIterator {
    boolean hasNext();
    Song next();
}

Defining Concrete Iterator Classes

InOrderPlaylistIterator: Traverses the playlist from start to end

import java.util.List;
import java.util.NoSuchElementException;

public class InOrderPlaylistIterator implements PlaylistIterator {
    private List<Song> songs;
    private int position;

    public InOrderPlaylistIterator(List<Song> songs) {
        this.songs = songs;
        this.position = 0;
    }

    @Override
    public boolean hasNext() {
        return position < songs.size();
    }

    @Override
    public Song next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }
        return songs.get(position++);
    }
}

ReversePlaylistIterator: Traverses the playlist from end to start.

import java.util.List;
import java.util.NoSuchElementException;

public class ReversePlaylistIterator implements PlaylistIterator {
    private List<Song> songs;
    private int position;

    public ReversePlaylistIterator(List<Song> songs) {
        this.songs = songs;
        this.position = songs.size() - 1;
    }

    @Override
    public boolean hasNext() {
        return position >= 0;
    }

    @Override
    public Song next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }
        return songs.get(position--);
    }
}

ShufflePlaylistIterator: Traverses the playlist in random order.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

public class ShufflePlaylistIterator implements PlaylistIterator {
    private List<Song> shuffledSongs;
    private int position;

    public ShufflePlaylistIterator(List<Song> songs) {
        this.shuffledSongs = new ArrayList<>(songs);
        Collections.shuffle(shuffledSongs, new Random());
        this.position = 0;
    }

    @Override
    public boolean hasNext() {
        return position < shuffledSongs.size();
    }

    @Override
    public Song next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }
        return shuffledSongs.get(position++);
    }
}

Defining the Aggregate Interface
PlaylistAggregate Interface: Defines methods to create iterators for different traversal strategies.

public interface PlaylistAggregate {
    PlaylistIterator createInOrderIterator();
    PlaylistIterator createReverseIterator();
    PlaylistIterator createShuffleIterator();
}

Defining Aggregate Class - Playlist

import java.util.ArrayList;
import java.util.List;

public class Playlist implements PlaylistAggregate {
    private List<Song> songs;

    public Playlist() {
        this.songs = new ArrayList<>();
    }

    public void addSong(Song song) {
        songs.add(song);
    }

    public List<Song> getSongs() {
        return songs;
    }

    @Override
    public PlaylistIterator createInOrderIterator() {
        return new InOrderPlaylistIterator(songs);
    }

    @Override
    public PlaylistIterator createReverseIterator() {
        return new ReversePlaylistIterator(songs);
    }

    @Override
    public PlaylistIterator createShuffleIterator() {
        return new ShufflePlaylistIterator(songs);
    }
}

Main
Here's how we can use PlaylistIterator and its iterators in the MusicApp

public class MusicApp {
    public static void main(String[] args) {
        Playlist playlist = new Playlist();
        playlist.addSong(new Song("Song 1", "Artist 1"));
        playlist.addSong(new Song("Song 2", "Artist 2"));
        playlist.addSong(new Song("Song 3", "Artist 3"));

        PlaylistIterator inOrderIterator = playlist.createInOrderIterator();
        System.out.println("In-Order Playlist: ");
        while (inOrderIterator.hasNext()) {
            System.out.println(inOrderIterator.next());
        }

        PlaylistIterator reverseIterator = playlist.createReverseIterator();
        System.out.println("Reverse Playlist: ");
        while (reverseIterator.hasNext()) {
            System.out.println(reverseIterator.next());
        }

        PlaylistIterator shuffleIterator = playlist.createShuffleIterator();
        System.out.println("Shuffled Playlist: ");
        while (shuffleIterator.hasNext()) {
            System.out.println(shuffleIterator.next());
        }
    }
}

Let's connect ๐Ÿง‘โ€๐Ÿ’ป

ย