Module 9

OOP & design patterns

Design questions probe whether you can organize code so change stays cheap. The foundation is smaller than the acronyms suggest: encapsulation(hide state behind behavior, so invariants can't be violated from outside), polymorphism(callers hold an interface and don't care which implementation answers), and the modern default of composition over inheritance — build objects out of collaborators rather than deep class hierarchies, because a has-a relationship can be swapped and tested, while a base class is forever.

Design patternsare named, pre-argued solutions to recurring shapes of change. The value in a conversation isn't reciting structure — it's matching the scenario to the pattern and naming the trade-off. Six cover the overwhelming majority of questions:

PatternThe scenario that calls for it
Strategyinterchangeable algorithms behind one interface, chosen at runtime — pricing rules, sort orders, retry policies
Factorycreation is complex or conditional; callers ask for the product, not the constructor
Observermany listeners react to one subject's changes without the subject knowing them — events, pub/sub, UI binding
Decoratoradd behavior (logging, caching, retries) around an object without touching its class — wrap and delegate
Adaptertwo incompatible interfaces must talk — a translating wrapper around the one you don't control
Singletonexactly one instance process-wide — and the honest caveat: global state hurts tests; injecting one shared instance is usually cleaner

🎛 Scenario → pattern quiz

Scenario 1 of 6

0/0 correct

The app must support several interchangeable pricing algorithms, chosen at runtime by name.

SOLID, one breath each

  • S — Single responsibility: a class has one reason to change; parsing and persistence don't share a home
  • O — Open/closed: add new behavior by adding code (a new strategy), not by editing a working switch statement
  • L — Liskov substitution: a subclass must be usable anywhere its parent is — no surprise exceptions, no tightened preconditions
  • I — Interface segregation: several small interfaces beat one fat one nobody fully implements
  • D — Dependency inversion: depend on abstractions; pass collaborators in (constructor injection) instead of constructing them inside

Worked example — strategy in twelve lines

class FlatFee:
    def cost(self, order): return 5.0

class PerItem:
    def cost(self, order): return 0.6 * len(order.items)

class Checkout:
    def __init__(self, shipping):   # injected — Checkout never changes
        self.shipping = shipping
    def total(self, order):
        return order.subtotal + self.shipping.cost(order)

# New shipping rule later? Add a class, register it. Checkout untouched —
# that's the open/closed principle with a name and an address.

Say it out loud

  • “The requirement is ‘new algorithms will be added’ — that's strategy, so additions don't modify tested code.”
  • “I'd compose rather than subclass here — I can inject a fake in tests and swap implementations later.”
  • “A singleton works, but it's global state — I'd rather construct one instance at startup and inject it.”