Skip to content

Module 15 — Exercises

Every exercise states its cognitive level, its task, and the observable result that counts as success. Compile with the course's flags, treating a warning as a failure:

g++ -std=c++17 -Wall -Wextra -Werror main.cpp -o exercise && ./exercise

Two habits from this module: write down the requirement before judging a design, and count what a change touches instead of describing how it feels.


Warm-up [L1 Understand]

W1 — Draw what you have

Task. Take a program of yours of at least four classes — your Lab 14 work will do — and draw its UML class diagram: classes, visibility, attributes, operations, and every relationship with its notation (dependency, association, aggregation, composition, inheritance) and multiplicities.

Success looks like. A diagram that can be checked line by line against the declarations. Every vector<Item> member is composition or aggregation and the diagram says which; every base class has a hollow arrow pointing at it; every stored reference that is not owned is an association.

Stuck? Section 2 of Concepts.


W2 — Six classes, one sentence each

Task. Write the responsibility of six classes from your own code as one sentence each. Then flag the ones whose sentence needs "and", and say for each flagged one whether the two clauses have different audiences or different rates of change.

Success looks like. Six sentences, a list of the flagged ones, and — this is the marked part — a decision for each flagged class: split it, or leave it and say why. "Leave it" is a legitimate answer when the audience is the same.

Stuck? Section 3 of Concepts.


W3 — Five violations, five expensive changes

Task. In one of your own subsystems, find one violation of each SOLID principle and name the change it would make expensive. If you cannot find five, say so and explain what you checked — an honest gap is a better answer than an invented violation.

Success looks like. Five entries of the form principle → the code → the requirement that would be expensive, and how many files it would touch.

Stuck? Debugging, Move 1, and section 1 of Concepts.


W4 — A strategy you can inject

Task. Implement a LendingRule strategy with two interchangeable implementations, injected into a Desk::lend. Then write a third implementation that returns fixed values, and use it to test that Desk::lend formats its output correctly.

Success looks like. Three implementations, one of them a test double, no caller edited when the second rule was added, and a demonstrated test that could not have been written before the interface existed.

Stuck? Section 11 of Concepts and examples/module-15/04-factory-and-strategy/.


Core [L3 Modify → L4 Analyze]

C1 — Replace a growing conditional, then prove it is closed

Task. Take a function of yours that switches on a kind, replace the switch with polymorphism or a strategy, and then add a new kind to prove the existing code is closed for modification.

Success looks like. The new kind is a new class plus one registration line; the diff for the existing files is empty; and the behaviour for the old kinds is unchanged (compare the output before and after).

Stuck? Section 4 of Concepts and examples/module-15/02-growing-conditional-to-polymorphism/.


C2 — "Fix this": the interface that breaks everyone

Task. This interface is used by a report generator that needs two of its six operations. Split it, and fix the two implementers so that neither writes a stub.

class LibraryService {
public:
    virtual ~LibraryService() = default;
    virtual void addItem(const std::string& title) = 0;
    virtual std::vector<std::string> allTitles() const = 0;
    virtual void lend(const std::string& title) = 0;
    virtual void giveBack(const std::string& title) = 0;
    virtual void emailMember(const std::string& address) = 0;
    virtual void printReceipt(const std::string& title) = 0;
};

Success looks like. A split that leaves each interface with one audience; two implementers with zero stubs; and a compiled demonstration that the read-only implementation can no longer be passed to a lending-desk caller (a static_assert or a commented line that does not compile).

Stuck? Section 6 of Concepts.


C3 — Two principles in tension, and a decision

Task. Find a place in your own design where two principles pull against each other — a fat interface you split into three that are now hard to find, a class you left doing two things because splitting it would create a meaningless pair, an injection you skipped because there is one configuration.

Success looks like. A decision, the cost of the chosen option, the cost of the option you rejected, and a condition that would make you reverse it — in four sentences, in the form at the end of the concepts page's section 8.

Stuck? Sections 8 and 14 of Concepts.


C4 — Trace a requirement and count

Task. Choose a plausible next requirement for your Lab 14 system (a new member category, a new item kind, a fine that is capped per member, a hold queue). Trace it through the code and count the sites it touches. Name the design cause of the highest count.

Success looks like. A list of the sites, a number, and the smell responsible — one of the five rows in Debugging, Move 1. If the number is one or two, say so, and choose a different requirement: the exercise is to find a real finding, not to manufacture one.

Stuck? Debugging, Move 1.


Challenge [L5 Design → L8 Evaluate]

X1 — A notification subsystem where observers disappear

Task. Design a notification subsystem for the library system in which subscribers can be created and destroyed at any time, and nothing is ever notified after it is destroyed. State who decides an observer is alive, and what happens at the moment it stops being.

Success looks like. A working subsystem, the design choice named (weak references and pruning, RAII unregistration, or subject-owned subscribers) with its cost, and a demonstration: a subscriber destroyed mid-run whose notifications stop, with the counters that prove it. (This carries Module 9's lifetime discipline into a pattern.)

Stuck? Section 12 of Concepts and examples/module-15/05-observer-and-lifetimes/.


X2 — Restructure a working subsystem to be open for extension

Task. Take a subsystem that works and restructure it so that a new implementation of its variation does not edit existing code. Measure the change cost before and after: pick the requirement, and count the files it would have touched, then the files it now touches.

Success looks like. Identical behaviour, demonstrated by comparing output; the before/after counts; and a named requirement that now touches one file instead of several. If the counts are the same, the refactoring did not improve the design — say so and explain what it did change.

Stuck? examples/module-15/02-growing-conditional-to-polymorphism/ and Debugging, Move 1.


X3 — Argue against a pattern you built

Task. Take a pattern you implemented in this module's exercises or lab, and write the case for removing it: what the simpler version looks like, what the pattern buys today, and what breaks if you take it out. Then decide — and either decision is acceptable if it is argued.

Success looks like. A genuine argument with the cost of removal named (the change that becomes expensive, the test that can no longer be written), the counter-case for keeping it, and a decision with a trigger for revisiting. This is the exercise that separates "knows the patterns" from "can judge them", and it is the one most often skipped.

Stuck? Section 13 of Concepts and examples/module-15/07-over-abstraction/.


How to check yourself

  • [ ] Every exercise compiles with -Wall -Wextra -Werror, with no warnings
  • [ ] Every judgement is written as decision, cost, rejected option, reversal condition
  • [ ] Every refactoring is demonstrated with the output compared before and after
  • [ ] Every change-cost claim is a count of files or sites, not an adjective
  • [ ] Every pattern in a design names the requirement that justified it
  • [ ] At least one pattern was considered and declined, in writing

Continue

Previous: Debugging · Next: Lab 15 · Module home: Module 15