Skip to content

Intermediate Project 4 — Playlist Manager

Difficulty: Intermediate · Estimated effort: 12–16 hours · Prerequisites: Module 9 — Composition and Association, Module 11 — RAII and Smart Pointers, Module 14 — The STL with OOP · Combines: ownership across two containers, a shared library, ordering operations, persistence

This is one of the designated integration projects. It must use, explicitly and in writing: multiple classes · relationships between classes · encapsulation · inheritance or composition · polymorphism · the STL · exception handling.

No complete solution is provided. The ownership question — who owns a track, who only refers to it — is the project, and it is not answered for you here.


1. Scenario

A radio station has a music library and many programmes. Tracks are added once and appear in any number of programmes; a programme can be deleted or re-ordered without touching the library; the library can be saved and reloaded; and each programme is exported as a running order sheet.

The station's previous tool kept tracks in a global array and played with dangling pointers after a programme was deleted. This project exists so that cannot happen.


2. Problem statement

Build a library of tracks and a set of playlists referring to them, with correct ownership, editing commands, total durations, export, and persistence. Deleting a playlist must not affect the library or any other playlist; the design must make that true rather than merely testing it.


3. User requirements

  • [ ] Add tracks to the library and see them listed with duration.
  • [ ] Create playlists and add tracks to them, by identifier.
  • [ ] Insert, remove and move a track within a playlist.
  • [ ] See each playlist's total duration and track count.
  • [ ] Delete a playlist and confirm the library is intact.
  • [ ] Export a playlist as a running order with positions and cumulative duration.
  • [ ] Save the whole station and load it back.

4. Functional requirements

  • [ ] Track with a unique identifier, title, artist and integer duration in seconds.
  • [ ] Library owning tracks via std::unique_ptr.
  • [ ] Playlist holding non-owning references, ordered, with insert/remove/move.
  • [ ] Station owning the library and the playlists.
  • [ ] Duration arithmetic in integer seconds, formatted at display time only.
  • [ ] A documented policy for what happens when a track is removed from the library while a playlist refers to it.
  • [ ] Persistence of tracks, playlists and their order, with a version marker.

5. Non-functional requirements

  • [ ] A track is destroyed exactly once, whichever order playlists and the library are destroyed in.
  • [ ] No raw owning pointers; raw pointers, if any, are clearly observers.
  • [ ] Playlist operations that fail leave the playlist exactly as it was.
  • [ ] Lookups by identifier are not linear scans through the whole library in the hot path — or if they are, you say so and justify it with a measurement.
  • [ ] Builds warning-free with g++ -std=c++17 -Wall -Wextra -Werror.

6. Suggested features

  • A play count and "last played" per track, stored on the library side.
  • A shuffle that is deterministic given a seed — and a reason why determinism is a gift in a test suite.
  • Playlists of playlists, with an explicit statement of what the outer one owns.
  • A smart-playlist rule ("all tracks under three minutes") evaluated on demand, and a note on whether it is a playlist at all.

7. OOP concepts involved

Concept Required Where it appears
Multiple classes yes Track, Library, Playlist, Station, duration formatting, error types
Relationships yes Library ◆ tracks; playlist ──> tracks (aggregation at most); station ◆ both
Encapsulation yes Duration cannot be negative; identifiers are unique; order is internal
Inheritance or composition yes Composition is the honest answer here — and saying why inheritance is wrong is part of the mark
Polymorphism yes A shared formatting/export interface used for both the running order and the library export
The STL yes std::vector for order, std::map for lookup, algorithms for totals and moves
Exception handling yes Unknown identifiers and file errors throw distinct types with context

8. Suggested design considerations

Answer the ownership question in the type system. Library owns; Playlist refers. Write that sentence in the header, then make the declarations match it. If a Playlist can outlive the Library, either forbid it by holding a reference to the library or document the rule.

Removal is the design hole. If a library track is removed, every playlist referencing it becomes a landmine. Choose: refuse while referenced, notify playlists, or remove references everywhere. Each is defensible; pick one, and put the check where the invariant lives.

Ordering is state, not incidental. Playlist order must survive save/load. Storing it as a position number is easy and corrupts on insert; storing the sequence itself is robust. Decide.

Integer seconds. Durations formatted as mm:ss at display time only. Adding formatted strings is the bug this constraint prevents.


9. Minimum viable version

Track, Library owning tracks, Playlist with add/remove and a total duration, and a main that builds a small library, makes two playlists, deletes one and prints the other.


10. Intermediate extension

Add ordering operations — insert, move up, move down — and the removal policy for library tracks, then verify with a test that a refused library removal leaves every playlist untouched.


11. Advanced extension

Add save/load of the whole station, including playlist order, then delete every playlist in a different order from the one they were created in and show that destruction is still correct. Then implement the smart-playlist rule and answer in writing whether it deserves to be a Playlist subclass or should be a separate query object.


12. Testing checklist

  • [ ] Adding the same track to two playlists does not copy it (checked with a copy counter)
  • [ ] Total duration equals the sum of the members, in seconds, for an empty playlist too
  • [ ] Insert at position 0, in the middle, and at the end each put the track where documented
  • [ ] Moving the first track up and the last track down are no-ops, and say so
  • [ ] Removing a track from a playlist leaves the library untouched
  • [ ] Deleting a playlist leaves the library and every other playlist untouched
  • [ ] Removing a library track referenced by a playlist behaves exactly as documented
  • [ ] Save → load reproduces order and durations exactly
  • [ ] Each track is destroyed exactly once at program end, under a shuffled deletion order

13. Documentation checklist

  • [ ] The ownership diagram, with the aggregation/composition choice justified in a sentence
  • [ ] The removal policy, with the reason and the rejected alternatives
  • [ ] Order representation and why it survives edits
  • [ ] The file format with a version marker and a round-trip example
  • [ ] One note answering: why is inheritance the wrong tool for Playlist here?

14. Self-evaluation rubric

Criterion Excellent looks like Weight
Ownership model One owner per track; deletion orders all safe; diagram matches code 30%
Removal policy Chosen, documented, tested, and safe for every referrer 20%
Ordering Order survives insert, remove and load; tested at the boundaries 15%
Interface Lookups by identifier; no leaks; const where it should be 15%
Failure handling Unknown ids and bad files rejected with context 10%
Documentation Ownership, policy and format written down, with the rejected alternatives 10%

Related: Intermediate projects · Project roadmap · All projects · Module 9 — Composition and Association