Challenges — 10 problems

← Module home · ★ to ★★★★. Each has a separated approach — design and commit before reading. Build the ★★ and above; every challenge ends with a defence sentence: the one sentence you’d say if asked “why this relationship?”


Approaches and solutions

A1. class Animal { public: Animal(const string& n) : name(n) {} virtual void speak() const = 0; const string& getName() const { return name; } virtual ~Animal() = default; private: string name; }; — implementers override speak (+override). Chorus: for (int i = 0; i < n; i++) cout << arr[i]->getName() << " says: "; arr[i]->speak();. Defence: Animal is abstract because no honest animal has no sound; every listed species refines the one contract, so the chorus works unchanged for any future animal.

A2. stats holds const Shape* best = nullptr; double bestArea = -1; and accumulates — all through the interface; “which kind” is answered by which pointer won, no type probing needed (if the report must name kinds, give Shape a pure name() const — data about the object through the contract, not a cast around it). Defence: the function reads rules through the interface; kinds are data, never branches.

A3. void generate(ostream& out) const { header(out); for (each item) formatRow(item, out); footer(out); } — non-virtual skeleton, four pure (or defaulted) hooks. MarksReport implements fetch/format; SalesReport likewise; the third report = one new subclass. Defence: the pipeline’s order is policy (fixed once) and the content is mechanism (per report) — the template-method split. (This is Lesson 2’s describe() pattern promoted to a framework.)

A4. Split by capability: Driller { virtual void drill() = 0; }, Painter { ... }, Welder { ... }, Polisher { ... } — each with a virtual destructor; DrillPress implements Driller only, and so on. workshop(vector<Driller*>&) drills every driller; the other loops likewise. Defence: no machine is asked to do work it doesn’t do; capability interfaces are admission tickets, and stubs become impossible. (Fat-base gallery #2, cured.)

A5. The board can’t copy pointers because two boards would then share tiles — deleting one board or clearing one grid destroys the other’s objects (double-delete on death, mutations visible across boards). clone() makes each subclass responsible for producing a fresh, correct copy of itself — the only place that knows its own fields. Cost per new subclass: one override (Tile* clone() const override { return new Kind(*this); }). Defence: ownership demands deep copies, and only the concrete class knows its depth. (The pattern’s proper name — the prototype/clone idiom — is worth writing down; smart-pointer copy machinery is next course.)

A6. Version A: class BankAccount { public: explicit BankAccount(Logger& log) : log(log) {} protected: void logEvent(const string& e) const { log.write(e); } ... }; — wait: the composition version needs no protected at all: private: Logger* log; and deposit() calls log->write(...). Version B: class LoggingBankAccount : public BankAccount overrides deposit… but deposit isn’t virtual (and shouldn’t be) — so version B must re-write each method and call the base version, doubling the interface. The answers: version A can log a future SavingsAccount (logging is inside the base behaviour, inherited automatically); version A can swap loggers at runtime (re-point the reference/pointer); version B can do neither without new subclasses per combination. Verdict sentence: logging is a service the account uses, not a species the account is — composition, twice over. (C6 exists because “inherit to add behaviour to every method” is the most tempting bad trade in the gallery.)

A7. map<string, Shape* (*)()> makers; — registerKind("circle", []() { return new Circle(); });-style registration (function pointers are enough; C7’s brief allows them — challenge C15 of the Algorithms module introduced the type). create looks up, returns makers.at(name)() or nullptr. When Triangle arrives: register one more name — nothing in the factory, the loop, or the callers changes; what must change: Triangle exists and someone registers it (registration is the one honest touchpoint). Defence: the registry is the extension point — closed code, open table.

A8. The sketch: Circle::collide handles the Circle-vs-Circle case directly, then needs to distinguish other — the honest beginner tool is a downcast probe (dynamic_cast<const Rect*>(&other)), which this course names but doesn’t adopt; the conceptual answer: single dispatch chooses on the left object only, so the right object’s kind requires a second decision — the double dispatch problem; real systems use the visitor pattern (a visit(Circle&)/visit(Rect&) double-table) or a kind enum inside the hierarchy boundary. The challenge’s real payload is the honest statement: polymorphism dispatches on one object per call; interactions between two unknowns need machinery beyond Lesson 2 — named, sketched, deferred. (Gallery #8’s mirror: knowing when NOT to fake it.)

A9. The class head comments: “Ownership: the host takes ownership of every registered plugin and deletes them all in its destructor (in reverse registration order). Plugins must not delete themselves and must outlive no call they didn’t survive.” Host guarantees: registration acceptance, ordered playAll attempts, deletion at shutdown (virtual destructor doing D4’s job). Plugin guarantees: the three contract clauses (DP9) plus statelessness across failed plays (a failed play leaves the plugin usable). Defence: the host is the owner and the contract is the border — third-party code is exactly the case where trust must be structural, not personal.

A10. The choice: composition (C8’s verdict logic — pricing rules swap at runtime and multiply quarterly). After: class Order { private: const Pricing* pricing; Region region; bool giftWrap; public: long long total() const { long long t = pricing->total(items, region); return giftWrap ? t + GIFT_FEE : t; } }; with Pricing implementers (MemberPricing, SalePricing…) — region as a parameter/data, not a subclass; gift-wrap as the add-on computed after (the decorator idea in one if, honestly small). Call sites: order.total() — no flags. The 17th rule: before — edit the 16-branch body (and re-test all 16); after — one new Pricing implementer + one registration. Defence: flags were kinds wearing booleans; the polymorphic pricing gives each kind its own class and the compiler its old job back. (Gallery #10 — the switch refactor — at its most realistic.)


Where next

— Engr. Dr. Muhammad Nadeem Majeed, Professor, Department of Data Science (PUCIT), University of the Punjab, Lahore · About the instructor