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¶
- [ ]
Trackwith a unique identifier, title, artist and integer duration in seconds. - [ ]
Libraryowning tracks viastd::unique_ptr. - [ ]
Playlistholding non-owning references, ordered, with insert/remove/move. - [ ]
Stationowning 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
shufflethat 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
Playlisthere?
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