Module 16 — Advanced OOP Capstone¶
After this module you have built, documented and tested a complete object-oriented application: requirements to design to implementation to evidence.
At a glance¶
| Prerequisites | Modules 1–15 — this module uses all of them |
| Estimated study time | 15–20 hours (the capstone is a project, not a lesson) |
| Lab | Lab 16 — the capstone |
| Assessment | Capstone rubric |
| Cognitive levels | L5 Design → L8 Evaluate — exit level L8 Evaluate |
| Self-assessment | Module quiz · Practice final |
Why this module matters¶
Every previous module taught one mechanism in isolation. Real software is what happens when they are all present at once, and the interactions are where the difficulty lives: ownership across components, failure while data is being loaded, a design that is clean until the third feature arrives.
This module is where you find out whether you can do it end to end — and where you discover that the honest answer to "how should this be designed?" is usually found by writing the second version, not by thinking harder about the first.
Three things are added that earlier modules touched only in passing, because a complete application needs them:
- Persistence. An application that forgets everything at exit is a demo. Here the running system loads its catalogue from a file and saves it back, with real handling of missing and corrupt data.
- Multi-file architecture. A program of fifteen classes is no longer one well-commented file. You will organise it into headers and source files, with namespaces and a build command that works reproducibly.
- Tests. Not a formality — the mechanism that lets you refactor a working system with confidence, which is exactly what the capstone asks you to do.
Learning objectives¶
By the end of this module you will be able to:
- Take a set of requirements and produce a documented design: classes, responsibilities, relationships and interfaces, in UML.
- Organise a multi-class program across headers and source files, with a reproducible build command, and explain the role of each translation unit.
- Explain and apply the One Definition Rule, and diagnose the link errors it causes.
- Define and use namespaces — including nested and anonymous ones — and explain what an anonymous namespace gives you.
- Read and write files with
std::fstream, in text and binary modes, and explain what each open mode does. - Detect and report stream failures (
fail,eof,bad) instead of silently continuing with bad data. - Persist and reload a collection of objects reliably, including validation of malformed input, and prove that a save → load round trip preserves the data.
- Use
std::stringstreamto convert between text and values. - Structure a project so that each class has one clear responsibility, and justify the boundaries you chose.
- Refactor a working design into a better one while tests continue to pass, and list concretely what improved.
- Write tests covering invariants, boundaries and failure cases — not only the happy path.
- Review your own design critically and write down what you would do differently, including one decision you would reverse.
Core concepts¶
- Requirements to design — responsibilities first, then classes, then relationships; code last.
- UML class diagram — the design artefact you are assessed on (Module 15).
- Translation unit — a source file after preprocessing; the compiler's unit of work.
- Header / source organisation — declarations in headers, definitions in source files, included carefully (Module 2).
- One Definition Rule (ODR) — a function may be declared many times but
defined once. Defining a function in a header produces
multiple definitionlink errors. - Namespace — a named scope for your project's types, so names cannot collide with the standard library or with third-party code.
- Anonymous namespace — gives internal linkage: the name is visible only in
that translation unit. The modern replacement for
staticfile-scope functions. includeorder and discipline — include what you use; avoid cycles with forward declarations (Module 9).- Build command — one explicit command that rebuilds the project from source.
This course uses
g++with files listed explicitly; aMakefileor CMake setup is an optional convenience layered on top of it. - Streams — the abstraction behind
std::cout,std::cin, and file streams; a stream is a source or sink of characters with a state. - Stream state —
good,fail,eof,bad. Checking it is how you notice that reading failed. std::ifstream/std::ofstream— input and output file streams.- Open modes —
in,out,app,trunc,binary; combining them wrongly is a classic way to destroy data. std::stringstream— an in-memory stream, for parsing and formatting text.- Round-trip integrity — save then load must reproduce the same state. The test that proves persistence actually works.
- Robust parsing — assume the file is hostile: hand-edited, truncated, from an older version. Validate and report, rather than crashing or silently corrupting (Module 13).
- Unit test — an automated check of one behaviour. In this course, small self-contained programs with clear pass/fail output.
- Boundary and failure tests — the cases that matter most: empty input, the maximum, the duplicate, the malformed record.
- Refactoring — improving structure without changing behaviour, made safe by tests.
- Design review — critically reading your own design, and saying what is wrong with it.
Module contents¶
| Section | What it contains |
|---|---|
| Introduction (this page) | Overview, prerequisites, objectives, concept inventory |
| Concepts and explanations | Multi-file organisation, the One Definition Rule, namespaces, streams and files, and testing |
| Worked examples | A small application split into files, then given persistence, then given tests |
| Code examples | examples/module-16/ — six programs, each with captured output |
| Misconceptions and mistakes | Wrong mental models about structure and files — "more files means a better structure", "a file format is trivial" — and the mistakes that follow from them |
| Debugging | Diagnosing the build as well as the program: reading linker symbols, include hygiene, treating a file as hostile input, and reproducing a bug as a failing test |
| Exercises | Three tiers, eleven tasks — from splitting a program into three files to refactoring under test |
| Lab 16 | The capstone itself: design diagram, patterns, persistence, multi-file layout, tests and design report |
| Challenge problems | Extensions beyond the rubric, for students who finish early |
| Self-assessment | Questions with explained answers, the Module quiz, and the practice final as the end-of-course check |
| Summary | The course compressed to what you must retain, and what to do next |
| Further reading | Books and official documentation |
The capstone at a glance¶
You will extend the running system into a complete application with:
- a documented design, as a UML class diagram, with ownership and responsibilities marked;
- a factory for creating items, a strategy for the lending or fine policy, and an observer for notifications (Module 15);
- persistence: load on start, save on exit, validating malformed input;
- a multi-file layout with namespaces and a documented build command;
- tests covering invariants, boundaries and failure cases;
- a short design report: what you abstracted, what you deliberately left concrete, and what you would change next time.
The full brief, milestones and rubric are under Projects.
What you can do after this module¶
- Build a complete, non-trivial application from requirements rather than from an exercise statement.
- Explain your design decisions to another engineer, including the ones you are unsure about.
- Diagnose a link error caused by an ODR violation, a missing source file, or a duplicated definition.
- Load data that a hostile user has edited, and fail gracefully.
- Refactor with confidence, because the tests will tell you if you broke behaviour.
Where this is used later¶
There is no later module: this is the end of the course. From here the natural next steps are outside it — a data-structures course (you will now understand why the standard containers are designed as they are), a course on concurrency (RAII and ownership are the foundation of thread-safe C++), or simply building something of your own.
If you want to keep going with this material: revise using Cheat Sheets and Practice Assessments, or return to a lab you rushed and rebuild it from scratch.
Common mistakes to watch for¶
| Symptom | Cause |
|---|---|
multiple definition of 'Library::count()' |
A function was defined in a header included by several translation units. Declare in the header, define in one source file |
undefined reference to 'Library::load(std::string const&)' |
A declared function has no definition, or its .cpp file was left out of the build command |
| Data is lost when the program restarts | Saving was not called, or an output stream opened with trunc before the load, or the parse silently failed |
| The file loads as garbage or empty | Reading a file that failed to open, without checking the stream state |
error: 'X' does not name a type in a header that clearly includes it |
An include cycle. Break it with a forward declaration |
Everything lives in main.cpp |
The design was never made explicit. Split by responsibility, not by line count |
| A file you hand-edited crashes the program | Parsing assumed well-formed input. Validate |
| Tests pass but the program is broken | The tests only cover the happy path, or they test what the code does rather than what it should do |
Skills checklist¶
Levels this module works at: [L5 Design → L8 Evaluate] — see How you will learn.
Tick an item only when the evidence exists:
- [ ] I can structure a multi-file application a stranger could navigate — evidence: the file tree, with the read order documented and the entry point findable in ten seconds
- [ ] I can separate public from internal interfaces — evidence: a public header that compiles when
placed alone in a one-line
.cppfile - [ ] I can apply the One Definition Rule, and diagnose its failure from the symbols — evidence: one
duplicate-symbol error fixed two ways, with the
nm -Coutput that found it - [ ] I can persist and load data with a documented format — evidence: the format documentation, with a version marker and an escape rule, and a round trip preserving every field on an empty catalogue
- [ ] I can handle malformed input without crashing or corrupting state — evidence: the three hand-edited corruptions, each reported with a line number, plus the counts showing the data was left untouched
- [ ] I can write tests that would catch a regression — evidence: a deliberate bug introduced, and the assertion that failed
- [ ] I can refactor a working system safely — evidence: a file split with the tests green before and after, and the tests unmodified
- [ ] I can evaluate my own design — evidence: the design report's named weakness and the decision I would reverse, with the cost of reversing it
Continue¶
Previous: Module 15 — SOLID and Design Patterns · All modules: Modules index · Capstone brief and rubric: Projects · Revision: Cheat Sheets