skipnothing/Design Patterns

Design Patterns

Observer, Strategy, Decorator, Factory, see them hiding in code you already write.

You'll need OOP basics, classes, interfaces, inheritance in any language.

Loading…
01

Behavioral Patterns

Observer, Strategy, State, patterns for runtime behavior

6/6
#1

Observer

Spot the polling-loop smell, implement subscribe/notify/unsubscribe on a class, and trace the lapsed listener leak that makes forgotten detach calls eat memory silently.

`setInterval(check, 100)` lives in every widget on a dashboard, each one asking the counter if it changed and mostly hearing 'no'. Ten widgets, one slow source, thousands of wakeups a minute for a number that ticks once.

14 min
#2

Strategy

Recognize the 'growing switch on type' smell, extract each branch into a swappable algorithm object or lambda, and spot the same shape inside `Array.sort`, Python `sorted(key=…)`, and Java `Comparator`.

A `process(type, amount)` method has gained a fresh `case` every release for six months. Card, PayPal, crypto, gift card, store credit, BNPL, each new payment type adds a branch and pushes the file past 200 lines.

12 min
#3

State

Rotate the multi-method `switch(mode)` matrix into one class per mode, push transition decisions onto the modes themselves, and trace the silent-failure shape where the host re-grows an `instanceof` ladder before delegating.

An audio player's `play()` method starts with `switch (this.mode)`, and so does `pause()`, and so does `lock()`. Four modes today, one `switch` per method, three methods, a fifth mode lands and you're touching every method to add the same branch.

14 min
#4

Command

Freeze each button-handler call into an object holding its own `execute` and `undo`, watch the same list of objects become an undo stack or a job queue when you swap the consumer, and spot the shape in Java `Runnable`, Swing `AbstractAction`, Redux actions, and Celery or BullMQ job queues.

An editor's `bold` button calls `doc.applyFormat(...)` directly. Add undo, and every method on `doc` grows a twin, every handler caches the old value, the document class doubles in size, and redo is still ahead.

14 min
#5

Chain of Responsibility

Pull each `if` from `checkAccess` into a one-method class holding one `next` reference, watch a request walk the chain choosing handle/pass/mutate-pass at every box, and spot the same shape in Express middleware, ASP.NET pipelines, Servlet filters, Python `try/except`, and DOM event bubbling.

Every new role drops a line into `checkAccess`. The function has grown past 40 lines of `if (user.role === '...')`, and every team that adds a role edits the same file.

14 min
#6

Mediator

Collapse N(N-1) peer imports into one central coordinator that holds the rules, watch the same shape inside MVC controllers, MediatR's `_sender.Send`, and `useReducer`'s `dispatch`, and recognize when the hub drifts toward a god object.

An email input calls `submit.disable()` whenever the value changes, add a password field and email grows an `if`; add remember-me and every older handler grows another. Four fields, twelve edges of coupling, and every new one re-edits the rest.

12 min
02

Structural Patterns

Decorator, Adapter, Facade, Proxy, patterns for combining objects

5/5
#7

Decorator

Trade a 2^N subclass table for N wrappers that share one interface, stack compression and encryption around a file source in any order, and spot the same nesting in `new BufferedReader(new FileReader(f))` and Python's `open()`.

A `FileDataSource` with `read` and `write` ships fine, until someone wants compressed saves, then encrypted ones, then both. By the third option you're naming a `CompressedEncryptedFileDataSource` and the class names are multiplying faster than the features.

14 min
#8

Composite

Collapse a recursive `if isinstance(node, Folder)` into one `size()` that both files and folders answer, watch the type check leave the caller and become recursion on the container, and weigh the transparency-versus-safety cost of putting `add()` on every node.

Your `total_size(node)` function opens with `if isinstance(node, Folder)`, recurses into the children, and re-asks the same question at every level. Add a zip-archive node and you're back editing that one function, and every other function that walks the tree.

14 min
#9

Adapter

Wrap a vendor's `dispatch(to, body, opts)` so two hundred call sites keep calling `send(msg)`, watch the translation live entirely inside the wrapper, and tell it from a decorator by the one thing that differs: the interface changes, not the behaviour.

Your whole app calls `notify.send(msg)`. The SMS vendor you just signed names the same action dispatch, wants a recipient and an options bag, and there are two hundred call sites between you and a library you can't edit.

10 min
#10

Proxy

Front an expensive or guarded object with a same-interface stand-in that decides when to create it, whether to allow the call, or whether a cached answer is enough, and spot the shape in JS `new Proxy`, ORM lazy loads, and gRPC stubs.

A photo grid builds a full-resolution `HighResImage` for all two hundred files the moment the folder opens, decoding megabytes the user scrolls past. The screen shows nine at a time; the rest loaded for nothing.

12 min
#11

Facade

Route a handler's five service calls through one method so callers depend on one class instead of five, reach past it straight to a worker for the rare case the method skips, and split the front before it bloats into a god object.

A checkout handler calls `inventory.reserve`, then `payment.charge`, then `shipping.schedule`, then emails the buyer, in that exact order. Every screen that places an order copies the same five-step dance and imports all five classes.

12 min
03

Creational Patterns

Factory, Builder, Singleton, patterns for object creation

4/4
#12

Factory Method

Move the `new` behind one overridable method, so a subclass decides which class gets built and callers depend only on the shared type.

A `planRoute` method opens with `new Truck()`, and so does the mobile endpoint, the admin re-run button, and the retry job. The day sea freight arrives, every one of those files has to learn the word `Ship`.

12 min
#13

Abstract Factory

Group a family of related products behind one factory object, so a single startup choice swaps the whole set and a mismatched combination can never compile.

Your toolbar builds each widget on its own line: `new WinButton()` here, a checkbox three files over, a menu in the render loop. Ship the macOS skin and one of those lines keeps making the Windows part.

12 min
#14

Builder

Replace a positional constructor with an object that collects named steps and a closing build() call, so every value is labeled where you read it, validation runs before the object exists, and the same step sequence can assemble different products.

A constructor with six arguments looks fine until you read `new Coffee(12, true, false, 2)` and cannot tell which value is the milk. Swap the two booleans and it still compiles, runs, and quietly brews the wrong drink.

12 min
#15

Singleton

Separate the two promises Singleton bundles, one instance and a global access point, so you keep the single object but hand it in through constructors instead of a static getter that nothing declares.

You call `Config.getInstance()` in the order service, again in the report job, again in three more classes. None of their constructors mention config, yet every one of them quietly depends on the same hidden object.

10 min
04

Patterns in Practice

Beyond the catalog

4/4
#16

Dependency Injection

Spot the class that builds its own collaborators with new, move that construction up into a constructor parameter so the same class turns testable with a fake and swappable in production, and tell true injection apart from a service locator that only looks like it.

An `OrderConfirmation` class builds its own `new SmtpMailer()` in the constructor, so every test needs a live mail server and switching providers means editing the class itself. The trap is building what you could be handed.

12 min
#17

Template Method

Extract the shared skeleton of two near-identical methods into a parent that calls overridable steps by name, recognize the same fixed-order shape in framework lifecycle hooks like a test runner's setUp/tearDown, and tell it apart from the run-time version you inject instead of inherit.

Two `export()` methods sit in two classes: both open a file, write a header, write each row, and close. The only lines that differ are how a row gets formatted, yet you copied the whole skeleton.

12 min
#18

Iterator

Pull a loop's index out into a separate cursor that answers next and hasNext, read the same protocol across Python's StopIteration, JavaScript generators, Java's hasNext, and Rust's Option, and tell an iterable apart from the one-pass iterator it hands you.

A `for (i = 0; i < shelf.books.length; i++)` loop works until the day `books` stops being an array. Reach into the backing array and every loop you wrote breaks once that collection becomes a tree or a stream.

12 min
#19

Anti-Patterns & When Not to Use

Weigh a pattern's up-front indirection against the flexibility it only repays when real variation arrives, name the three ways that bet goes wrong (an abstraction built too early, one bent with a flag per case, a favourite pattern swung at every problem), and wait for the rule of three before you abstract.

A three-line `switch` on payment type grew into an interface plus four classes across four files, though nothing varies yet that the `switch` couldn't hold. You paid the cost of flexibility the code never spends.

12 min
View full 19-topic curriculum