problemhardood

Object-Oriented Design for a Musical Jukebox

HardUpdated: Aug 2, 2024

Problem

Design a musical jukebox using object-oriented principles that plays songs from a collection (CDs or playlists), supports queuing tracks, and allows a user to interact with playback via a CD player and a track selector.

Solution

We reuse the database-oriented music library design for persistent storage (artists, albums, tracks). This note focuses on the runtime object model: playlists, playback components, user sessions, and track selection.

1. Requirements Analysis

Functional Requirements:

  • Maintain a catalog of songs (via CDs and/or playlists).
  • Support creating and managing playlists (queueing tracks, peeking next track).
  • Play, pause, and select specific tracks via a CD player or track selector.
  • Allow user sessions where a user can control playback (processOneUser).

Data/Metadata Requirements:

  • Song metadata: name, artist, year, genre.
  • Playlist: ordered collection (queue) of Song objects and current track pointer.
  • CD: physical media concept to group tracks (placeholder).

Non-Functional Requirements:

  • Clear separation of responsibilities (single responsibility principle) between playback, storage, and UI/session control.
  • Extensibility to support additional input sources (files, streaming) and multiple users.

2. Use Case Diagram

Actors: User, System (playback engine).

Use Case Summary: A User selects a source (CD or playlist), queues or selects tracks, controls playback (play/stop/next), and can switch between playlists or CDs. The System handles actual audio playback and maintains current track state.

graph TD
  subgraph JukeBoxSystem
	UC_Select(Select Source: CD / Playlist)
	UC_Queue(Queue Track)
	UC_Play(Play / Pause / Stop)
	UC_Next(Skip / Next Track)
	UC_GetCurrent(Get Current Track)
  end
  User --> UC_Select
  User --> UC_Queue
  User --> UC_Play
  User --> UC_Next
  User --> UC_GetCurrent
  System --> UC_Play
  System --> UC_GetCurrent

JukeBox Diagram

3. Class Diagram

Below are detailed class descriptions followed by a compact diagram.

Classes (detailed):

  • Song: Value object for a single track. Key attributes: songName, artist, year, genre. Minimal behavior: metadata accessors.
  • Playlist: Holds a queue of Song objects and an optional current track pointer. Behaviors: getNextTrackToPlay(), queueUpTrack(Song), removeTrack(), peekCurrent().
  • CD: Represents a collection of songs on a physical medium. Behaviors: listTracks(), eject()/insert() (placeholders).
  • CDPlayer: Responsible for actual playback control. Holds a Playlist and/or CD reference. Behaviors: playTrack(Song), pause(), stop(), setPlaylist(), setCD(). It is the runtime bridge between data (Playlist/CD) and playback engine (System).
  • TrackSelector: Small component that maintains currentSong and methods to set/get it. Used by JukeBox to coordinate which song should be played next.
  • User: Identifies the active user/session interacting with the jukebox. Holds id and name; may be extended with preferences.
  • JukeBox: High-level orchestrator that aggregates CDPlayer, current User, CD collection, and TrackSelector. Exposes processOneUser(User) and getCurrentTrack().
  • Facilities / CD collection: Represent auxiliary data like a user's available CDs; kept as collections in JukeBox.
classDiagram
  class Song { +String songName +String artist +int year +String genre }
  class Playlist { +Song track +Queue~Song~ queue +getNextTrackToPlay() +queueUpTrack(Song) }
  class CD { +List~Song~ tracks +listTracks() }
  class CDPlayer { +Playlist p +CD c +getPlaylist() +setPlaylist() +getCD() +setCD() +playTrack(Song) }
  class TrackSelector { +Song currentSong +setTrack(Song) +getCurrentSong() }
  class User { +long ID +String name +getName() +setName() }
  class JukeBox { +CDPlayer cdPlayer +User user +Set~CD~ cdCollection +TrackSelector ts +getCurrentTrack() +processOneUser(User) }
  Playlist --> Song
  CD --> Song
  JukeBox --> CDPlayer
  JukeBox --> User
  JukeBox --> TrackSelector
  CDPlayer --> Playlist
  CDPlayer --> CD

4. Activity Diagrams

Activity: Play Next Track

graph TD
  A[Current track ends or user presses next] --> B[TrackSelector requests next track]
  B --> C[Playlist.peek()]
  C --> D[CDPlayer.playTrack(next)]
  D --> E[Update TrackSelector.currentSong]

Activity: Queue New Track

graph TD
  U[User selects track to queue] --> Q[Playlist.queueUpTrack(track)]
  Q --> R[If player idle -> play next]
  R --> S[Update UI]

5. High-Level Code Implementation

The original Java code is preserved as the primary skeleton. Below are cleaned Java snippets and a Python equivalent with type hints.

Java (cleaned skeleton)

public class Song {
	private String songName;
	private String artist;
	private int year;
	private String genre;
	// getters/setters
}

public class Playlist {
	private Song track;
	private java.util.Queue<Song> queue;
	public Playlist(Song track, java.util.Queue<Song> queue) { this.track = track; this.queue = queue; }
	public Song getNextTrackToPlay() { return queue.peek(); }
	public void queueUpTrack(Song s) { queue.add(s); }
}

public class CD { private java.util.List<Song> tracks; }

public class CDPlayer {
	private Playlist p;
	private CD c;
	public Playlist getPlaylist() { return p; }
	public void setPlaylist(Playlist p) { this.p = p; }
	public CD getCD() { return c; }
	public void setCD(CD c) { this.c = c; }
	public CDPlayer(Playlist p) { this.p = p; }
	public CDPlayer(CD c, Playlist p) { this.c = c; this.p = p; }
	public CDPlayer(CD c) { this.c = c; }
	public void playTrack(Song s) { /* playback glue */ }
}

public class TrackSelector { private Song currentSong; public TrackSelector(Song s) { currentSong=s; } public void setTrack(Song s) { currentSong = s; } public Song getCurrentSong() { return currentSong; } }

public class User { private String name; private long ID; public String getName() { return name; } public void setName(String name) { this.name = name; } public long getID() { return ID; } public void setID(long iD) { ID = iD; } public User(String name, long iD) { this.name = name; this.ID = iD; } public User getUser() { return this; } public static User addUser(String name, long iD) { return new User(name, iD); } }

public class JukeBox {
	private CDPlayer cdPlayer;
	private User user;
	private java.util.Set<CD> cdCollection;
	private TrackSelector ts;
	public JukeBox(CDPlayer cdPlayer, User user, java.util.Set<CD> cdCollection, TrackSelector ts) { this.cdPlayer = cdPlayer; this.user = user; this.cdCollection = cdCollection; this.ts = ts; }
	public Song getCurrentTrack() { return ts.getCurrentSong(); }
	public void processOneUser(User u) { this.user = u; }
}

Python (type-hinted skeleton)

from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from typing import List, Deque, Set
from collections import deque

@dataclass
class Song:
	song_name: str
	artist: str
	year: int
	genre: str

class Playlist:
	def __init__(self, track: Song | None = None) -> None:
		self.track: Song | None = track
		self.queue: Deque[Song] = deque()
	def get_next_track_to_play(self) -> Song | None:
		return self.queue[0] if self.queue else None
	def queue_up_track(self, s: Song) -> None:
		self.queue.append(s)

class CD:
	def __init__(self, tracks: List[Song] | None = None) -> None:
		self.tracks: List[Song] = tracks or []

class CDPlayer:
	def __init__(self, playlist: Playlist | None = None, cd: CD | None = None) -> None:
		self.playlist = playlist
		self.cd = cd
	def play_track(self, s: Song) -> None:
		pass

class TrackSelector:
	def __init__(self, current_song: Song | None = None) -> None:
		self.current_song = current_song
	def set_track(self, s: Song) -> None:
		self.current_song = s
	def get_current_song(self) -> Song | None:
		return self.current_song

class User:
	def __init__(self, name: str, id_: int) -> None:
		self.name = name
		self.id = id_

class JukeBox:
	def __init__(self, cd_player: CDPlayer, user: User, cd_collection: Set[CD], ts: TrackSelector) -> None:
		self.cd_player = cd_player
		self.user = user
		self.cd_collection = cd_collection
		self.ts = ts
	def get_current_track(self) -> Song | None:
		return self.ts.get_current_song()
	def process_one_user(self, u: User) -> None:
		self.user = u

JukeBox Diagram

You can download the complete code from github.

Comments