Skip to content

Module 4 — Challenge Problems

Open-ended on purpose, and mostly written. The deliverable is a decision with a reason, because that is what design work produces — and because a decision you cannot write down is a preference, not a design.

Do them on paper first. Every one of them gets shorter when you argue with yourself in prose, and the prose is what a reviewer will ask for.


X1 — a rule that spans two types [L5 Design]

A campus has lockers. A locker may be assigned to a member, but only if that member has no overdue item. A member may hold at most one locker. A locker's number never changes.

The rule "only if that member has no overdue item" is the interesting one, because it is not about lockers or about members — it is about a state of affairs that neither type can see alone.

Deliverable, in writing, then in code:

  1. State every invariant, one sentence each, and name the type that owns it.
  2. For the overdue rule: decide who owns it. Argue three options and pick one: Locker asks the Member; the Member is asked first and the Locker trusts the answer; a third type (a LockerDesk) owns the assignment and checks both.
  3. Show the interaction for a refusal, as a numbered conversation: "the caller asks the desk to assign locker 12 to Alice; the desk asks Alice whether she has overdue items; …".
  4. Name one way your design can still be wrong even if every type is correct in itself.
  5. Implement the assignment operation, with the rule enforced in the one place you chose.

Success looks like. One-sentence invariants; a defended ownership decision that names what would break if the rule moved; an interaction that leaves both objects unchanged on refusal; and an honest (4).

How you will be judged. On (4). The rule spans two objects, and some possible bad state always remains — for example, a member who becomes overdue after the locker was assigned. Being able to say what your design does not prevent is the difference between a design and a belief.

Where this leads. The third-type answer in (2) is Module 9 (composition and association) and Module 15 (a policy object). Choosing it here, and being able to say why, is worth marking.


X2 — review a justification you did not write [L4 Analyze]

examples/labs/lab-04/solution/justification.md contains three deliberate admissions:

  1. Book::describe() writes to std::cout — "a type that writes to std::cout has decided how it will be presented, which is a caller's business";
  2. Member keeps describe() for the same reason;
  3. the author would change Catalogue::find to return a const Book*.

Deliverable — a written review:

  1. For each of the three, state the actual harm: what a caller can do, or is prevented from doing, because of it. "It is impure" is not harm; name a concrete consequence.
  2. For each, propose the smallest change that removes the harm, and say what it costs.
  3. For admission (3) specifically: the alternative is not only const Book*. Give two other designs that remove the leak, one of which removes the need to return a pointer at all.
  4. Write the three sentences you would say to the author, in order, starting with what the justification got right.

Success looks like. Three concrete harm statements, three minimal fixes with their costs, two alternative designs for (3), and a review that keeps the author willing to listen.

Full credit includes noticing that (1) and (2) are the same problem, and that a describe() for a whole catalogue does not make it acceptable in a value type.

Why this matters. Reviewing somebody else's interface is easier than reviewing your own, and it is the same skill. Every reason you give here is a reason you will eventually give about your own code — and the order in (4) is the difference between a useful review and an argument.


X3 — refactor to an interface that cannot be misused [L5 Design]

Take the Roster from Exercises, C2 — the one with at, setCount, an unguarded add and an unguarded kick — and design its replacement.

Deliverable:

  1. The invariant, in one sentence, and the rule about capacity.
  2. The complete public interface, with a caller's sentence for each member. No member may exist whose sentence ends nowhere.
  3. The refusal semantics for each operation that can fail: what it returns, and what is true of the object afterwards.
  4. The count of public members, before and after, and a one-line note on each deletion.
  5. Your own attack test passes: write out five attacks on the new interface and show that each fails to compile or returns false.
  6. The cost: one paragraph on what the sealed version makes impossible for callers that the old version allowed. Be specific — this is a real loss for somebody, and naming it is the professional part.

Success looks like. Three or four public members, no leaks, total refusals, a passed attack test, and a cost paragraph that names a caller who is now worse off.

How you will be judged. On (4) and (6). Interfaces get better by getting smaller, and the price is always paid by somebody. A design review that does not mention who pays is selling something.


Stretch (optional)

  • S1. Design the same Playlist twice: once with each public member justified by a caller's sentence, and once as a struct with public data and free functions. Write four sentences on when each is the right choice, and what the struct version cannot promise.
  • S2. Take a class of yours and make every member public, then compile everything that uses it and read what still works. Then restore the encapsulation and notice that the compiler tells you nothing about the difference. Write three sentences on where that difference is felt.
  • S3. Write a const-correct class that holds a cache (use mutable legitimately), then write the paragraph that would convince a sceptical reviewer. If you cannot write it, the mutable is unjustified.

Evaluating your own answer

Use this on every design above, and on your own lab work.

Criterion A strong answer A weak answer
Invariant One sentence, stated above the class, true at construction and preserved by every operation A comment listing validations performed at various call sites
Enforcement Each rule is enforced in exactly one place, and you can point at the line The rule is "checked where needed" — which means somewhere it is not
Interface Every public member has a caller's sentence; deletions outnumber additions Accessors mirror the fields one-for-one; nothing was removed
Leaks No returned mutable reference, iterator, pointer, or signature naming the storage "The caller can read it, but not write it" — usually false within two functions
Refusal A refused operation leaves every object unchanged, and the reason is available false for five different causes, or a half-applied change
const-correctness Every observing function is const; a const& parameter can print the whole object One non-const function quietly blocks every read-only use
Trade named You can say who is worse off and how the workaround looks The design is presented as a pure improvement
Uncertainty At least one member is flagged as arguable, with both sides Every decision presented as obvious

Diagnostic. If Invariant and Enforcement are weak, the problem is conceptual: re-read §2 of Concepts and run the attack test on somebody else's class before designing your own. If Interface and Leaks are weak, the problem is mechanical and the fix is a table plus five minutes of deleting. If Trade named and Uncertainty are weak, you are designing defensively — which is not a design, and is worth knowing about yourself before a review tells you.


Next: Self-assessment — nine questions, with answers and a re-test schedule. · All modules