Skip to content

Module 8 — Challenge Problems

Open problems. More than one defensible answer exists, and the reasoning is assessed as much as the code. Each one is designed so that a quick answer is visibly insufficient.


X1 — the refusal that should not have been a function [L4 Analyze]

The problem. Six interfaces follow. Each has exactly one function that some plausible implementation cannot honour. Find it, name the implementation that breaks it, and choose one of the three remedies — remove the function, split the interface, or accept it — with a reason.

class Readable  { virtual std::string read(); };
class Writable  { virtual void write(const std::string&); };
class Closeable { virtual void close(); virtual bool isOpen() const; };
class Cache     { virtual void store(key, value); virtual value load(key); virtual void evictAll(); };
class Clock     { virtual Instant now() const; virtual void set(Instant); };
class Comparable{ virtual int compareTo(const T&) const; virtual bool isEqual(const T&) const; };

What a good answer contains.

  • The implementation that cannot honour it, named concretely — a read-only file for Writable::write, a monotonic clock for Clock::set, a view over a remote store for Cache::evictAll().
  • A decision between the three remedies, with the cost of each. Removing a function shifts work to callers; splitting it adds types and forces callers to name more than one; accepting it means one implementation lies — and it can be the right answer, if the lie is small and the alternative is worse.
  • One case where accepting the lie is the right call, argued. This is the half of the answer most students omit: not every unsupported operation deserves an interface split.
  • The test you would apply in review. One sentence, applicable to a diff.

Would make it weak. Splitting every interface on principle; treating Comparable's two functions as a problem when one is derivable from the other; or an answer that never names a concrete implementation.

Deciding it for yourself. Ask what the caller must do after your change. An interface split moves work from implementations to callers, and both sides pay something.


X2 — the interface a caller can actually use [L5 Design]

The problem. Design the read side of the campus gradebook. Requirements, from three different people, all of them reasonable:

  • a report needs every student's name and final grade;
  • a transcript needs one student's courses and grades;
  • an export needs the whole dataset streamed, because the full set does not fit in memory;
  • and the chair wants aggregate statistics without seeing any individual row.

Four callers, four shapes of access, and one underlying store.

What a good answer contains.

  • The interfaces, and a defence of how many there are. One Gradebook with four functions that all return vectors is the answer to reject — it forces the streaming export to materialise the whole dataset, and it gives the statistics caller access to individual rows it should not have.
  • What each caller must now name. A caller needing both a transcript and statistics names two interfaces. Say so plainly and say whether it is worth it.
  • The streaming question, which is not an interface question at all: a self-contained stream is either an abstraction of its own (an iterator, Module 14) or a callback interface (a visitor), and the answer should say which and why.
  • Where the interface stops. The chair's statistics could be one function — or three, one per statistic — and the choice says something about who owns the definition of a "statistic". Say which and why.
  • The test: a fifth caller arrives needing "students with a grade above X, streamed". Which existing interface serves it, and what changes?

Would make it weak. One interface with everything; interfaces named after implementation layers (PersistenceGradebook); or an answer in which the statistics caller can reach individual rows anyway, which defeats the requirement it was written for.


X3 — evaluate "program to an interface, not an implementation" [L8 Evaluate]

The problem. GoF's advice, repeated as a rule, in the form of an evaluation. Where is it right? Where is it over-applied? What does it cost?

What a good answer contains.

  • A case where an interface was decisive. examples/module-08/02-interface-contracts/main.cpp is one: the NotificationService never names a channel, and a test double is just another implementation. Say concretely what would have been impossible without the interface — not merely harder.
  • A case where an interface is a mistake. A Money, a Date, a Point, a small value type with one implementation and no prospect of a second. The costs are real: no value semantics, an indirection per call, and every container raised to a lifetime question.
  • The compile-time alternative, named. A template parameter substitutes an implementation with no indirection and no ownership problem, and it is often the better answer for small value-like types. Module 12 is that tool; the evaluation must say when it is the wrong one (when the choice must be made at run time).
  • A distinction between an imagined second implementation and a planned one. "We might need another one" is a guess; a name, an owner and a date is a plan. This distinction is the memorable part of the answer, and it is worth stating in one sentence a reviewer could apply.
  • A verdict with conditions. Not "interfaces are good, but not always", which is not a verdict. Something like: write an interface when the implementation will be chosen at run time, or when a second one exists as a name rather than a hope; otherwise take the compile-time alternative or a concrete class.

How you will be judged. Whether a reader who disagrees can follow the reasoning, whether both worked cases are concrete, and whether your decision criteria could be applied mechanically to a real diff.


The rubric

Applied to X1, X2 and X3 alike. Score each row 2 (done), 1 (present but weaker), 0 (absent).

Criterion
1 The problem is stated in one sentence, in your own words, before any design appears
2 Every claim is checkable — a compiler message, a program, a named source
3 The decision is stated as a decision, with what it costs and who pays
4 The rejected alternative is described fairly enough that its advocate would recognise it
5 The condition that would reverse the decision is named
6 The answer ends with something a reader can do: a rule, a test, a file to change

X1 has a defensible set of right answers, so rows 2 and 3 carry most of the marks. X2 and X3 have no single right answer, so rows 4 and 5 do.


Evaluating your own answer

Write these three lines before reading any suggested approach:

My decision:
The trade-off I accepted:
What I would do differently with more time:

Then compare against the "what a good answer contains" list, item by item, and mark each one present, missing, or present but weaker than the list. The third category is where the marks are: an answer that says an interface "decouples" callers but cannot say what the caller must now name instead has the vocabulary and not the model.

Two questions to close with:

  1. Could a reader who disagreed with you still follow your reasoning? If your answer only works for someone who already agrees, it is a preference, not an argument.
  2. What would make you change your mind? A concrete observation — a second implementation with a name, a measurement, a caller that cannot use the interface — not "more experience".

Next: Self-assessment — ten questions, including two compile predictions and a hierarchy with four defects. · All modules