Module 15 — Summary¶
The essentials¶
- The criterion is change localisation. A design is good if the next requirement lands in one place. Everything else in this module — UML, the five principles, the patterns — exists to make that measurement smaller, and each of them can also make it larger.
- The measurement is a count. Pick a plausible requirement, trace it, count the files and sites it touches. "Five places in four files" is a design finding; "this class feels too big" is not.
- UML is drawn from the code, not from the intention. Composition means the part's lifetime is the
whole's, aggregation means it can outlive it, association means a reference with no ownership, and the
multiplicity is part of the statement. If
Cataloguestoresvector<Item>, the diamond is filled; if it storesvector<shared_ptr<Item>>, it is hollow and the lifetime question has moved. - One sentence, without "and". The refined test is one audience, one reason to change. Three audiences with three reasons to open a file is the finding; the size is a symptom. When the two clauses share an audience and a rate of change, splitting is cost with no buyer.
- Closed for modification means closed with respect to what varies. A new kind becomes a new class with no edits to existing ones; a new operation then edits the interface and every implementation. You choose an axis, so choose it from the requirements rather than from a principle.
- Inheritance is for substitution, not reuse. A derived type may not strengthen preconditions, weaken postconditions, or break invariants — and when it cannot honour an inherited operation, the bug appears in client code written against the base. The fault is in the abstraction, and the fix is a capability that only the types which can honour it implement.
- Many small interfaces, each owned by its users. A class forced to write stubs is the symptom, and every client depending on operations it never calls is the other half of the cost.
- The policy owns the interface it depends on. Point the arrow the other way and a test double becomes possible, which is what makes dependency inversion worth its indirection. The symptom of getting it wrong is a unit test that needs a database, a network, or a real file.
- The principles pull against each other. More interfaces means more indirection; openness means more to read through. The skill is choosing and stating the cost, not maximising any one of them.
- A pattern is a named solution with a cost. The name is the deliverable: strategy, observer, factory communicate a shape in one word. The cost is always some of: more types, more indirection, a parameter threaded further, a lifetime question relocated.
- Factory, strategy and observer, in this course's C++. A factory is a function that centralises
creation rules and expresses ownership in its return type —
unique_ptr<Item>, not a raw pointer nobody owns. A strategy is interchangeable behaviour behind an interface, injected where the context is known. An observer is a list of subscriptions, and the question it does not answer is the one that matters: who decides an observer is still alive. - The observer's lifetime question has three answers, each with a cost: weak references and pruning
(a
lock()per notification), RAII unregistration (the subscriber must keep the token), or subject-owned subscribers (nobody outside can subscribe). Choosing is the design; discipline is not. - Singletons are usually a decision about convenience wearing the language of uniqueness. The defence — "only one exists, and everybody needs it" — is a hidden global, an untestable component, and a construction-order hazard. Create one at the composition root and pass it; then "only one" is true by construction.
- A pattern applied where the variation is not real is a cost with no buyer. The test: does the variation exist now, and is it expected to grow? Reaching for the name before the problem is the mistake this module's last example exists to correct — and it is the same mistake as refusing a pattern the requirements have earned.
- Evaluation is a decision with costs named: the context, the choice, its cost, the rejected option and its cost, and the condition that would reverse the decision. An evaluation with no numbers is a preference; an evaluation with no weakness is not one at all.
The change-cost traces worth keeping¶
| Requirement reaching the Lab 14 system | Before | After |
|---|---|---|
| A fine depending on member category as well as item kind | 5 sites in 4 files | the rule class, and one line at the composition root |
| A fifth kind of item | the enumerator, three switches, the record reader | one class, one registration line |
| A new notification subscriber | not possible — no notification point existed | one class, one subscription |
Keep the habit, not the numbers: your system's counts are yours, and producing them before and after is what turns "this is better now" into an argument.
The five principles, as symptoms rather than virtues¶
Read this column as a diagnostic you can run on code, with the fix implied by the symptom:
| You can see this in the code | The principle | The change that becomes local |
|---|---|---|
| One class holding the rule, the storage and the report | Single responsibility | Split by audience; each change lands in one |
The same switch on a kind in three functions |
Open/closed | New kinds stop editing existing code |
| A derived type throwing from an inherited operation | Liskov substitution | The operation moves to a capability |
| Implementers writing stubs they cannot honour | Interface segregation | Interfaces narrow to one audience each |
| A policy constructing the class it depends on | Dependency inversion | A test double becomes possible |
Further reading¶
- Design Patterns: Elements of Reusable Object-Oriented Software, Gamma, Helm, Johnson and Vlissides — the catalogue itself. Read it as a description of problems and forces, not as a list of things to put in a program; the introduction's account of when each applies is the part that has aged best.
- Head First Design Patterns (2nd ed.), Freeman and Robson — the friendliest treatment of the same material, and unusually honest about over-use. Good for the patterns this module did not cover (decorator, template method, composite).
- Clean Architecture, Robert C. Martin — the source of the SOLID acronym, with dependency inversion developed at the level of systems rather than classes.
- Refactoring: Improving the Design of Existing Code (2nd ed.), Martin Fowler — the mechanics of changing a working design safely, which is what the lab's change-cost trace is measuring. The catalogue of smells is a useful companion to this module's symptom table, with one caution: a smell is a prompt to look, not a defect.
- Applying UML and Patterns (3rd ed.), Craig Larman — UML in the service of a process, from requirements to design, which is what Module 16's capstone requires.
- A Philosophy of Software Design, John Ousterhout — the counterweight to pattern-driven design, arguing for deep modules and against the class explosion that shallow abstraction produces. Read it alongside this module's section 13.
- cppreference: language support — for the ownership and lifetime rules that every pattern in C++ has to respect.
- Effective Modern C++, Scott Meyers — Item 34 and the chapters on
unique_ptrandshared_ptr: the ownership vocabulary that makes a C++ factory, observer or decorator what it is.
Before you move on¶
Check these, with evidence rather than memory:
- [ ] I can produce a UML class diagram from my own code, and every relationship on it matches a declaration — evidence: the diagram, with multiplicities, checked line by line against the code
- [ ] I can state the change cost of a chosen requirement as a number, before and after a refactoring — evidence: the two counts, written down
- [ ] I can state each class's responsibility in one sentence, and I have acted on the ones that needed "and" — evidence: the sentences, and the split or the written reason not to split
- [ ] I have a class implementing an interface it fully honours, with no stubs — evidence: the implementer, and a compile error when the wrong pair is combined
- [ ] I have a dependency injected, and a test that could not have been written without it — evidence: the test double, and the assertion it makes
- [ ] I have a pattern, a rejected pattern, and the requirement behind each — evidence: two written paragraphs, one for the pattern I kept and one for the pattern I declined
- [ ] I have an observer design that answers who decides an observer is alive — evidence: the chosen mechanism, its cost, and a subscriber destroyed mid-run whose notifications stopped
- [ ] I have one decision written in the evaluation form, including a cost and a reversal condition — evidence: the five lines, taken from my own lab
Then move on to Module 16 — Advanced OOP Capstone, which is where these judgements are made about a whole system. The capstone's design report asks for exactly what Lab 15 asked for, at the scale of an application: the decisions, the alternatives rejected, the costs accepted, and the one decision you would reverse. If the reversal is hard to write, that is the honest starting point — Module 16's own milestone 4 is where it gets answered.
Related: Module 16 — Capstone · Projects and capstone rubric · Labs · Cheat sheets
Continue¶
Previous: Self-assessment · Next: Module 16 — Advanced OOP Capstone · Module home: Module 15