Object-Oriented Design

Design Patterns

Named, reusable solutions to recurring design problems: creational, structural, and behavioral.

Level: advanced

What is it?

A design pattern is a named solution to a design problem that shows up repeatedly, described with its trade-offs. The classic catalog (the 'Gang of Four') groups 23 patterns into creational (how objects are made: Factory, Builder, Singleton), structural (how they are assembled: Adapter, Decorator, Facade), and behavioral (how they interact: Observer, Strategy, Command, Iterator).

Why does it matter?

Patterns give teams a shared vocabulary — saying 'this is an observer' conveys the structure in one word — and they capture solutions that survived many projects. They are useful only when the problem is real; forcing a pattern onto code adds indirection without benefit.

Mental model

A cookbook of techniques, not recipes. 'Emulsify' is a technique you apply to mayonnaise, to a sauce, or to a dressing. A pattern is that: an idea you adapt to your ingredients, not code you paste.

How it works

  • Strategy: swap an algorithm behind an interface (sorting order, pricing rule).
  • Observer: subscribers register interest, and the publisher notifies them on change (events, UI updates).
  • Factory: hide which concrete class is created behind a function or method.
  • Decorator: wrap an object to add behavior while keeping the same interface (logging, caching, compression).
  • Adapter: convert one interface into another so incompatible pieces can work together.
  • Singleton is the most overused: it is global state in disguise and makes testing hard. Prefer injecting a single shared instance.

Steps

  • Spot a recurring design problem
  • Match it to a named pattern
  • Adapt the structure to your types
  • Check the trade-offs
  • Skip the pattern if a function will do

Code example: Observer: publishers do not know their subscribers

class EventBus:
    def __init__(self):
        self._subs = {}

    def subscribe(self, topic, fn):
        self._subs.setdefault(topic, []).append(fn)

    def publish(self, topic, payload):
        for fn in self._subs.get(topic, []):
            fn(payload)

bus = EventBus()
bus.subscribe("order", lambda o: print("email", o))
bus.subscribe("order", lambda o: print("stock", o))
bus.publish("order", 7)      # email 7, then stock 7

Real-world usage

  • Event systems, UI frameworks, and message buses (Observer)
  • Middleware chains and I/O wrappers (Decorator)
  • Plug-in points and configurable algorithms (Strategy, Factory)

Common mistakes

  • Reaching for a pattern before there is a problem it solves.
  • Using Singleton for convenience and inheriting hidden global state.
  • Implementing the textbook structure literally when a function or a lambda would do.

Interview questions

Strategy versus State pattern?

Both delegate to an interchangeable object. Strategy lets the caller choose an algorithm from outside. State lets an object change its own behavior as its internal state changes, with the state objects usually managing the transitions.

Why is Singleton controversial?

It is global mutable state with a nicer name: it hides dependencies, makes tests interfere with each other, and complicates concurrency. Injecting one shared instance gets the benefit without those costs.

What problem does the Observer pattern solve?

It decouples a source of events from the parties interested in them: subscribers register with the subject and are notified on change, without the subject knowing who they are.

CategoryQuestion it answersExamples
CreationalHow is the object made?Factory, Builder, Singleton
StructuralHow do the parts fit together?Adapter, Decorator, Facade
BehavioralHow do they talk to each other?Observer, Strategy, Command

Prerequisites

Related concepts

Continue learning

Browse the full computer science library