Skip to content

Module 8 — Exercises

Every exercise states its level, its task, and the observable result that counts as success. If you cannot tell whether you succeeded without reading a solution, the exercise is defective — tell the course maintainers.

Solutions point at programs you have already been given. Attempt each exercise before you look.


Warm-up [L1 Understand]

W1 — abstract, concrete, or interface?

Task. Classify each of these, and say what the deciding line is:

  1. class Shape { virtual double area() const = 0; }; plus two data members
  2. class Notifier { virtual bool send(...) = 0; }; with nothing else
  3. class Employee { long long monthlyPay() const; }; with no virtual anywhere
  4. class Exporter { virtual std::string footer() const = 0; }; with a definition of footer
  5. class Report { std::string render() const; protected: virtual std::string title() const = 0; };

Success looks like. Five classifications (abstract base class, interface, concrete class) and, for each, the single line that decides it. For 4 and 5, the sentence must mention that a definition does not affect abstractness.

Stuck? Concepts §4 and §3.


W2 — write the contract in words

Task. For PaymentMethod in examples/module-08/03-payment-methods/main.cpp, write down: the precondition of pay, the postcondition of pay on success, the postcondition on refusal, and the invariant a wallet maintains across calls.

Success looks like. Four statements in words, each one checkable by observing the program's output — no restatement of the function names.

Stuck? Concepts §2.


W3 — predict the compile errors

Task. Before compiling anything, write what the compiler will say about each of these. Then compile them and compare — including whether the error is at the declaration or at the use site.

struct Shape { virtual double area() const = 0; };

struct A : Shape {};                                  // (1)
struct B : Shape { double area() { return 1; } };     // (2)
struct C : Shape { float area() const; };             // (3)

Shape s;                                              // (4)
A* a = new A;                                         // (5)
std::vector<Shape> v;                                 // (6)

Success looks like. Six predictions with the line each error points at, and one sentence on why (2) produces two messages while (3) produces one.

Stuck? Misconceptions and mistakes — the real messages are all there.


Core [L2 Apply] · [L3 Modify] · [L4 Analyze]

C1 — turn the item family into a contract [L2 Apply]

Task. Take your Lab 6 item family (Book, Journal, Dvd under a BaseItem) and make BaseItem an abstract base class: no default implementation of loanDays() or of any per-kind rule, a virtual destructor, and one non-virtual public function that uses the pure virtuals to produce a line of output — Module 8's NVI shape.

Success looks like. BaseItem item; fails to compile with the compiler's own message; loanDays() through a BaseItem& gives the derived answer with no switch; and the shared reporting function is written once and never edited to add a kind.

Stuck? examples/module-08/01-shape-abstraction/main.cpp is the reference pattern.


C2 — split a wide interface [L4 Analyze]

Task. Here is an interface someone wrote for a fleet-management backend:

class Device {
public:
    virtual ~Device() = default;
    virtual bool connect();
    virtual void disconnect();
    virtual std::string read();
    virtual bool write(const std::string& text);
    virtual void restart();
    virtual double batteryLevel() const;
    virtual bool isOnline() const;
};

Run a contract audit on it: for each function, name an implementation you would plausibly be asked to write that cannot honour it. Then split the interface into the smallest set of capabilities that makes every function honourable, and show one class implementing two of them.

Success looks like. An audit table with at least three "no" rows, a split into capabilities named after what a caller wants to do (not after a device model), and a paragraph naming the cost of the split — the caller that needs several capabilities must now name several types, or a combined interface must exist.

Stuck? Concepts §6; the Notifier/PaymentMethod pair are two worked examples of the small-interface end of the range.


C3 — the missing definition [L3 Modify]

Task. The program below compiles cleanly and fails at link time. Find and fix it, then say which of the two fixes you chose and why.

class Sink {
public:
    virtual ~Sink() = 0;
    virtual void write(const std::string& message) = 0;
};

class EventSink : public Sink {
public:
    ~EventSink() override = default;
    void write(const std::string& message) override { (void)message; }
};

int main() {
    EventSink sink;
    sink.write("x");
}

The two fixes are: define Sink::~Sink(), or make the destructor protected and non-virtual. Both make the program link.

Success looks like. A link error observed and recorded (the compiler is silent, and on this toolchain the message names no symbol), the fix, and a paragraph arguing between the two options in terms of who may delete a Sink.

Stuck? Misconceptions and mistakes, Mistake 5.


C4 — remove the meaningless field [L5 Design]

Task. Do §7 of Concepts for real: take the payroll hierarchy from examples/module-06/01-payroll-hierarchy/main.cpp, and replace the base's monthlyPaisa_ — meaningless for a Contractor, which is constructed with 0 — with capability interfaces. Keep “a payroll loop over a container of everyone who is paid” working, and keep printing an identity card for all employees, paid or not.

Success looks like. A diagram with two interfaces and three implementing classes; no constructor that receives a value its kind does not have; a payroll loop with no switch and no base field; and a written list of the three things that improved plus the repetition the refactor introduced, since the shared id_/name_ storage now exists twice.

Stuck? Concepts §7 has the before/after diagram and the three sentences of improvement.


Challenge [L5 Design] · [L7 Refactor]

X1 — design the running system's item interface [L5 Design]

Task. Design the final interface for the campus catalogue's items, which must support: loan periods that differ per kind; an overdue charge that differs per kind; a "reserve" operation that only some kinds support; and a report that lists everything without knowing the kinds.

Deliver: (a) the diagram, with stateful bases distinguished from interfaces; (b) the contract in words — preconditions, postconditions, invariants — for the charge operation; (c) the decision about reserve, in one paragraph: in the interface as a refusal, in a separate Reservable interface, or not in the model at all; (d) the change needed to add "e-resource with an access expiry" next term, and how many existing files it touches; (e) the sentence saying what you would do differently if the set of item kinds were configured at run time from a database.

Success looks like. No pure virtual function that some kind cannot honour; a report written only against the read-side interface; and (c) argued from the caller's side — what the caller must now ask, rather than what the interface must now contain.

How you will be judged. The rubric. (c) and (d) carry most of the marks: the first is the interface-segregation decision, and the second is whether the design actually delivers extensibility.


X2 — flatten a hierarchy that grew [L7 Refactor]

Task. Construct a five-level hierarchy on purpose — a plausible one, such as Item → Lendable → Physical → Printed → Textbook — and make it work. Then flatten it to at most three levels, and report what each removed level was contributing.

Constraints: the flattened version must keep every capability its callers use; protected data must be gone; and each remaining level must satisfy the is-a test without a qualification.

Success looks like. Both versions working; a table with one row per removed level saying what it contributed and where that contribution went (a base operation, an interface, a strategy object, or nothing); and at least one place where flattening made the code worse, reported honestly rather than hidden.


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

Task. The phrase is GoF's, and it is repeated as a rule. Write an evaluation of it: where it is right, where it is over-applied, and what it costs.

Use at least two concrete cases from this course: one from examples/module-08/ where depending on an interface was the difference between extensible and not, and one where an interface is not warranted — a Money, a Date, a Point, a type with exactly one implementation and no prospect of a second.

Cover: what a run-time interface costs (value semantics, an indirection, the ownership question of Module 11) versus what a compile-time alternative gives (Module 12 is that alternative); when a second implementation is imagined rather than planned; and how you would decide, in review, whether an interface is earning its keep.

Success looks like. A verdict with conditions, two worked cases, and at least one place where you recommend not writing an interface, argued rather than asserted.

How you will be judged. Whether a reader who disagrees can follow the reasoning, and whether your criteria could be applied to a real diff.


Stretch (optional)

  • S1 — "What changes if…" Shape::describe() were made virtual as well? Name one thing that becomes possible and one guarantee that is lost.
  • S2 — "What changes if…" Notifier::send returned void and signalled failure by throwing? List the call sites that change, then check your answer against Module 13's rule for when an exception is right.
  • S3 — "What changes if…" Exporter::footer()'s definition were removed, leaving the function pure with no body? Predict the compiler's and the linker's behaviour, then test both.

Solutions

Exercise Reference
W1, W3, C1 examples/module-08/01-shape-abstraction/main.cpp — a contract, four implementations, and the errors that mark its boundaries
W2 examples/module-08/03-payment-methods/main.cpp — a contract with failure in it, and a caller that knows nothing about the methods
C2 examples/module-08/02-interface-contracts/main.cpp — a one-function interface with three implementations, one of them a test double
C3 §4 of Concepts and Mistake 5: the link error, both fixes, and what each prevents
C4 §7 of Concepts — the before/after diagram and the three sentences of improvement
X1, X3 No published answer: both are design arguments with more than one defensible verdict. The rubric is the marking scheme
X2 examples/module-08/04-abstract-base-and-interface/main.cpp shows the two-role shape a flattened hierarchy usually ends up resembling

X1 and X3 have no published answer on purpose. Compare with the rubric after writing your own.


Next: Lab 8 — the catalogue gets an abstract item interface, and a per-kind policy object it can replace at run time. · All modules