Free course · 36 lessons · Beginner
Design Patterns, Explained Simply
SOLID and all 23 classic design patterns, plus the patterns modern frameworks are built on, explained in plain language with everyday analogies, a before-and-after for every pattern, and one example you can run in Python, TypeScript, Java or PHP. Learn when to use each pattern, when not to, and where you have already used it.
1. Foundations: Thinking in Patterns
What design patterns are, the two ideas behind almost all of them, the five SOLID principles, and dependency injection, each with a small example you can run in Python, TypeScript, Java or PHP.
- What a Design Pattern Is (and Isn't) — Patterns are named, reusable answers to design problems that keep coming back. Learn what one contains, the three families, and why the name matters as much as the code.
- Program to Interfaces, Favour Composition — The two ideas behind almost every pattern: depend on what an object can do, not what class it is, and build behaviour by combining objects instead of deep inheritance trees.
- SOLID, Part 1: Single Responsibility and Open/Closed — The S and O of SOLID: give each class one reason to change, and add new behaviour by adding code rather than editing working code.
- SOLID, Part 2: Liskov, Interface Segregation, Dependency Inversion — The L, I and D of SOLID: subclasses must keep their parent's promises, interfaces should stay small, and high-level code should depend on abstractions.
- Dependency Injection Without a Framework — Pass an object its collaborators instead of letting it create them. You get testable code with no container, no annotations and no magic.
2. Creational Patterns: Creating Objects Flexibly
Factory Method, Abstract Factory, Builder, Prototype and Singleton: five ways to decide which object gets created, how it is assembled, and how many exist.
- Factory Method — Let subclasses decide which concrete object to create, so the code that uses the object never names its class.
- Abstract Factory — Create whole families of related objects that must match, without the code that uses them knowing which family it got.
- Builder — Assemble a complex object step by step with readable, named steps and one final validation, instead of a constructor with ten positional arguments.
- Prototype — Create new objects by copying a preconfigured one, and learn why the difference between a shallow and a deep copy decides whether the pattern helps or hurts.
- Singleton (and Why It's Often an Anti-Pattern) — Guarantee one instance with global access. Learn how it works, why it makes code hard to test, and the usually better alternative: create one instance and pass it in.
3. Structural Patterns: Fitting Objects Together
Adapter, Bridge, Composite, Decorator, Facade, Flyweight and Proxy: seven ways to combine objects into larger structures while keeping them flexible.
- Adapter — Make a class with the wrong interface work where your code expects another, by wrapping it in a small translator.
- Bridge — Split one large hierarchy into two independent ones, what something is and how it's done, and connect them with composition so they can grow separately.
- Composite — Treat a single object and a group of objects the same way, so code can work with whole trees, like folders, menus or org charts, through one interface.
- Decorator — Add behaviour to an object, such as logging, caching or retries, by wrapping it in objects with the same interface. Stack the wrappers in any combination without subclassing.
- Facade — Give a complicated subsystem one simple front door, so most callers can do the common task with one call instead of learning ten classes.
- Flyweight — Save memory when you have huge numbers of similar objects by sharing the heavy, unchanging part between them and keeping only the small, unique part in each.
- Proxy — Put a stand-in in front of an object to control access to it: load it lazily, check permissions, cache results, or call it over the network, without the client noticing.
4. Behavioral Patterns: How Objects Share Work
Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor and Interpreter: eleven ways objects divide responsibilities and communicate.
- Chain of Responsibility — Pass a request along a chain of handlers until one of them deals with it, so the sender doesn't need to know who will.
- Command — Turn a request into an object, so you can queue it, log it, retry it and, most usefully, undo it.
- Iterator — Walk through the items of a collection one at a time without knowing how it's stored: an array, a tree, a database cursor, or pages from an API.
- Mediator — Stop objects from talking to each other directly. Route their communication through one coordinator, so each object only knows the mediator.
- Memento — Save and restore an object's state as snapshots, without exposing its private fields to the code that stores the snapshots.
- Observer — Let an object announce events to any number of subscribers, without knowing who they are or what they do, so new reactions can be added without touching the source.
- State — Let an object change its behaviour when its internal state changes by moving each state's rules into its own class, instead of a switch statement in every method.
- Strategy — Define a family of interchangeable algorithms behind one interface and choose one at runtime, so the code that uses them never needs an if/else to pick.
- Template Method — Write the skeleton of an algorithm once in a base class and let subclasses fill in specific steps, so every variant follows the same order of steps.
- Visitor — Add new operations to a set of classes without changing them, by putting each operation in its own visitor object that the classes accept.
- Interpreter — Represent the rules of a small language as a tree of objects, and evaluate the tree. This is useful for filters, pricing rules and other mini-languages.
5. Modern Patterns and Choosing Well
The patterns modern frameworks are built on (Repository, Unit of Work, Middleware, Pub/Sub, Null Object, Specification), the anti-patterns to avoid, and a practical guide to choosing the right pattern.
- Repository and Unit of Work — Hide data access behind a collection-like interface, and group changes so they are saved together in one transaction.
- Middleware and Pipelines — Process a request through a chain of small, reusable steps (logging, auth, rate limiting) where each step can act before and after the next one, or stop the request.
- Pub/Sub and the Event Bus — Decouple publishers from subscribers completely with named topics and a broker in between, so services can react to events without knowing who produced them.
- Null Object and Specification — Two small patterns with a big effect: replace null checks with an object that does nothing, and turn business rules into small objects you can combine and reuse.
- Anti-Patterns and Over-Engineering — The most common ways patterns go wrong: god objects, singletons as global variables, service locators, premature abstraction and using a favourite pattern for everything.
- Choosing the Right Pattern — A practical cheat sheet: start from the symptom in your code, find the matching pattern, and tell the look-alike patterns apart.
6. Capstone Projects
Apply several patterns together: refactor a tangled checkout without changing its behaviour, and design a notification system with undo.
- Capstone: Refactor a Tangled Checkout — Refactor a checkout full of if/else chains into Strategy, Factory and Decorator, and prove the behaviour didn't change.
- Capstone: A Notification System with Undo — Design a notification system that reacts to events, sends through vendors with different APIs, and lets admins undo a broadcast, using Observer, Adapter and Command.