Library · Summary & review

Design Patterns

By the Gang of Four. The 1994 catalog that named the moves: what I keep from it, what has died, and why you speak GoF without knowing it.

FR EN
Design Patterns book cover, Gang of Four

Design Patterns

Design Patterns: Elements of Reusable Object-Oriented Software

7.2 /10

« Dry as a manual, dated as 1994 C++, and yet the entire vocabulary of the trade comes from here. »

  • AuthorsGamma, Helm, Johnson, Vlissides · le Gang of Four
  • OriginalAddison-Wesley, 1994 · 395 pages
  • EditionSingle edition (1994), examples in C++ and Smalltalk
  • This page~10 min read
Book rating across 5 dimensionsIdeas10/10Practical7/10Readability5/10Aged well7/10Examples7/10

The 23 names that gave the trade its vocabulary: you speak GoF without knowing it, like prose.

Why this book

The scenery first. A design pattern is a reusable solution to a design problem that keeps coming back, with a name so you can talk about it. This book is the founding catalog: 23 patterns, published in 1994 by four authors (Gamma, Helm, Johnson, Vlissides), hence their "Gang of Four" nickname and the "GoF" abbreviation you will meet everywhere. My Head First Design Patterns notes (HFDP, the illustrated, playful textbook that teaches these same patterns) kept pointing at this book: HFDP is the fun retelling, this is the source.

It reads like a dictionary because it is one: one entry per pattern, fixed format. But chapter 1 and the chapter 2 case study are real prose, and they contain the two principles everything else hangs from.

The ideas that stick

1A pattern is, first of all, a name

The book borrows the idea from Christopher Alexander, an architect of buildings: a pattern "describes a problem which occurs over and over again in our environment, and then describes the core of the solution" (ch. 1). Four mandatory elements: a name, a problem, a solution, the consequences. And the authors insist the first one is the hardest: "Finding good names has been one of the hardest parts of developing our catalog" (ch. 1).

Why so much fuss about names? Compression. Before: "you know, the object that wraps another one and adds scrolling without the wrapped one knowing". After: "a Decorator". A design discussion drops one level of abstraction lower every time a structure has no name. That is the actual product of the book: not the techniques (they existed), the shared vocabulary.

2The two commandments everything hangs from

Chapter 1 sets two principles in bold, and the 23 patterns are variations on them.

  • "Program to an interface, not an implementation" (ch. 1): never declare a variable as an instance of a concrete class; depend on the abstract type, so the concrete one can change behind your back.
  • "Favor object composition over class inheritance" (ch. 1): inheriting is "white-box" reuse: the subclass sees the parent's guts, and "any change in the parent's implementation will force the subclass to change". Composing is "black-box" reuse: you assemble parts of which you only see the interface, the contract.

Both commandments, before and after, on a case we have all written:

// ✗ BEFORE: concrete class + inheritance to vary
class Report {
  private MySQLExport $export = new MySQLExport();  // frozen concrete
}
class PdfReport extends Report { ... }
class EncryptedPdfReport extends PdfReport { ... }   // the tree runs away

// ✓ AFTER: one interface + parts you assemble
class Report {
  public function __construct(
    private Export $export,      // interface: MySQL, CSV, S3...
    private Format $format,      // interface: PDF, HTML...
  ) {}
}
new Report(new S3Export(), new EncryptedPdf());   // combine, no tree

The before builds a class tree that grows with every combination (PDF, encrypted PDF, encrypted compressed PDF...). The after combines independent parts: three exports and three formats give nine combinations for six classes, and tomorrow's variant breaks nothing.

The honesty matters: the authors observe that "designers overuse inheritance as a reuse technique, and designs are often made more reusable (and simpler) by depending more on object composition" (ch. 1). Thirty years of fragile base classes proved them right.

3One question behind 23 answers: what must be able to vary?

The book's selection guide hides its deepest idea: each pattern encapsulates one thing that changes, so the rest doesn't have to. Five examples, each time the definition then an everyday case:

  • Strategy: a family of interchangeable calculations behind one interface; what gets encapsulated is the algorithm. Your shipping fees: standard post, express or store pickup, three different calculations for the same question "how much?", swappable without touching the cart:
    interface ShippingFee { compute(cart): float }
    class StandardPost { compute(cart) { /* carrier price grid */ } }
    class StorePickup  { compute(cart) { return 0 } }
    
    total = cart.total() + shipping.compute(cart)  // whichever one it is
  • State: an object that changes behavior with its internal state, as if it "changed class". An e-commerce order: the same "Cancel" button does completely different things depending on whether the order is in the cart, paid, or shipped:
    // the order CARRIES its state: an object, replaced at each step of its life
    class Order {
      state = new InCart()                      // starting state
      pay()    { this.state = new Paid() }    // changing state = swapping the object
      ship()   { this.state = new Shipped() }
      cancel() { this.state.cancel() }         // delegates to the current state
    }
    
    // each state knows what "cancel" means for itself
    class InCart  { cancel() { emptyCart() } }
    class Paid    { cancel() { refund() } }
    class Shipped { cancel() { requestReturn() } }
    
    order.pay()
    order.cancel()   // → refunds, because the current state is Paid. Zero if.
  • Iterator: a way to walk a collection without knowing its internal structure:
    // three sources stored completely differently underneath
    orders = [101, 102, 103]                // in-memory array
    orders = Order::fromDatabase()          // SQL result, row by row
    orders = readHugeCsv('orders.csv')     // generator: never all in RAM
    
    // the SAME loop works for all three: each source knows how to walk itself
    foreach (orders as o) { process(o) }
    The day the array becomes an SQL query, the loop does not move one line: that is what the pattern buys;
  • Observer: an object notifies a list of subscribers when it changes, without knowing them (detailed in idea 5):
    // inside the mechanism: a list of subscribers + one loop, that is THE WHOLE pattern
    class Subject {
      subscribers = []
      subscribe(fn)  { this.subscribers.push(fn) }
      notify(info)   { for (fn of this.subscribers) fn(info) }
    }
    
    stock = new Subject()
    stock.subscribe(updateProductPage)   // the product page wants to know
    stock.subscribe(backInStockEmail)    // the customer alert too
    stock.notify('back in stock')        // both react; stock knows neither of them
    
    // your everyday addEventListener is exactly this machinery
    button.addEventListener('click', updateCart)
    A newsletter works the same: the sender does not know who subscribed, it sends, everyone reacts;
  • Mediator: objects stop talking to each other and all go through a central coordinator. A group chat: everyone writes to the room, nobody messages each member one by one:
    // without a mediator: every field knows every other field (n² links, hell to modify)
    
    // with one: fields only know the form, which orchestrates
    class Form {
      signal(what) {
        if (what === 'date') {
          timeField.reload()        // available slots depend on the chosen day
          submitButton.disable()    // until a time is picked again
        }
      }
    }
    
    dateField.onChange = () => form.signal('date')
    // dateField has no idea timeField exists: only the central switchboard knows

You may have noticed: these five examples come from the same online shop. That is deliberate, and that is the lesson: one real application naturally summons five patterns, each called in by its own problem (fees that vary, an order that changes stage, stock that notifies). Above all, never do the reverse: the "ultimate Cart class" stacking all five for sport is the pattern fever of idea 7. The Lexi chapter (idea 4) is the long version of this same lesson.

So the working question in front of your own code is never "which pattern can I use here?" but "what is going to change?". Find the axis of variation, wrap it in an object, and you usually rediscover one of the 23 without opening the book.

So what does the ideal cart look like?

Not like a class bristling with patterns: like a small architecture where each one holds the exact post where it earns its keep. The skeleton:

// the cart itself: NO pattern. The most central class is the dumbest one.
class Cart { add(product, qty) ; total() }

interface ShippingFee { compute(cart) }              // STRATEGY: fees vary
class FreeShippingOver implements ShippingFee {      // DECORATOR: the promo stacks
  __construct(base, threshold) { ... }                //   on top of any carrier
}
class Order { state ; cancel() { state.cancel() } }  // STATE: life stages
class Events { subscribe() ; publish() }             // OBSERVER: paying triggers
                                                      //   email, stock... unknown to it
class Checkout {                                     // FACADE: the single entry point
  __construct(shipping, events) { ... }
  placeOrder(cart) { ... }
}

// the assembly: every concrete choice happens at the root, never inside the classes
checkout = new Checkout(new FreeShippingOver(new StandardPost(), 50), events)

Three things to notice. Each pattern answers a nameable problem (fees vary, the promo stacks, the order has stages, paying must trigger the unknown, the controller wants one button). The central class has no pattern. And the absentees are a choice: no Singleton (the root assembly injects everything), no Command (nobody asked to replay step by step). A good design shows in its absent patterns as much as its present ones.

The step-by-step version is built in the hands-on project "Build the shop, one pattern at a time" (in French), and the story of its design is on the blog.

4Lexi: eight patterns born from one document editor

Chapter 2 is the living heart of the book: the authors design Lexi, a WYSIWYG document editor ("what you see is what you get": the screen shows the final page directly, like Word), and each concrete problem gives birth to a pattern, in context.

  • a document is text inside rows inside columns, recursively → Composite, the pattern that treats the single element and the group through the same interface (one Glyph class for characters and groups alike: you ask a letter or a whole page to "draw yourself");
  • line-breaking algorithms must be swappable, including TeX's (Knuth's typesetting system, famous for its perfect line breaks) → Strategy (the Compositor);
  • add a border or a scrollbar without subclassing everything → Decorator, the wrapper that adds a responsibility to an object without modifying it, stackable (border around scrolling around page);
  • support Motif and PM look-and-feels (the interface skins of 1990s Unix) → Abstract Factory, the factory that produces a whole family of matching objects (buttons, menus, bars of the same theme) without the client knowing the concrete classes ("there's no longer anything in the code that mentions Motif by name", ch. 2);
  • menus, buttons and shortcuts trigger the same action, with undo → Command, the action turned into an object you can store, replay or undo (a history list with Execute/Unexecute: that is your Ctrl+Z);
  • spell-check a structure without polluting it → Iterator (to traverse) + Visitor, the operation you carry across a structure without adding anything to it (the spell-checker visits every word; tomorrow the word counter will visit the same way).

Read this chapter even if you skip the catalog: it shows patterns as answers to felt problems, not as shapes to impose.

5Observer, the pattern that ate the world

One object changes; an open-ended set of others must follow, without the first knowing them. The book's example: a spreadsheet and a bar chart showing the same data object, each refreshing when the user edits the other, while "the spreadsheet and the bar chart don't know about each other" (Observer). The subject keeps a list of subscribers and notifies; that is the whole machine. You already write it every day without thinking: button.addEventListener("click", myHandler) is exactly this: the button is the subject, your function is the subscriber, and the button knows nothing about it.

The consequence the book underlines: the coupling is "abstract and minimal", so a low-level subject can inform a high-level observer without wrecking the layering. Every event listener, every pub/sub queue, every reactive UI state you used this week descends from this chapter. The trade-off is also named: push (the subject sends details, less reusable) versus pull (the observer asks, sometimes inefficient).

6Same silhouette, four intentions

Adapter, Decorator, Proxy, Facade all look alike in a diagram: an object standing in front of another. The book spends its end-of-chapter discussions separating them, because what names a pattern is not its shape, it is its intent:

  • Adapter changes the interface: it makes two things fit that were never designed to ("makes things work after they're designed");
  • Decorator keeps the interface and adds responsibilities, stackable at runtime;
  • Proxy keeps the interface and controls access (lazy-load the image, count the references);
  • Facade invents a simpler interface over a whole subsystem (one Compile() over scanner, parser, generator).

The book is full of these scalpel cuts: a decorator changes the skin, a strategy changes the guts; a MacroCommand is a Composite of Commands; shared State objects "are essentially flyweights". The patterns are not 23 bricks, they are 23 intentions that combine.

7The one process tip: start simple, evolve when it hurts

The creational chapter compares its five patterns on the same toy program (a maze game), and closes with the only real process advice in the book: "often, designs start out using Factory Method and evolve toward the other creational patterns as the designer discovers where more flexibility is needed" (Creational discussion). Translation: don't install an Abstract Factory on day one; subclass one creation method, and upgrade when reality asks.

The same restraint closes chapter 1: "A design pattern should only be applied when the flexibility it affords is actually needed" (ch. 1). The authors of the patterns book warned against pattern fever on page 31. Few readers made it to that sentence.

A proud young developer opens an enormous toolbox filled with 23 ornate golden tools, facing a plain wooden board with one single tiny nail sticking out
23 golden tools, one nail. The book itself warns: only when the flexibility is actually needed.

8The classic that apologizes for existing

The conclusion opens with a sentence no marketing department would allow: "It's possible to argue that this book hasn't accomplished much" (Conclusion). No new algorithms, no theory: "it just documents existing designs". The authors claim exactly one contribution: naming what good programmers were already doing, so it could be taught and discussed.

And the humility is not a pose. The book took years to exist: a thesis, workshops, a summary rejected by a conference. Even the names changed along the way: Singleton was first called Solitaire, and Facade used to be Glue. Their final admission: "finding patterns is much easier than describing them" (Conclusion). Anyone who has ever written docs knows that one is true.

Three things I didn't know

My take, honestly

Honest about my level first: I am a web developer, I do not spend my days quoting computer science books. The names, though, I have met them forever: Strategy and Singleton live in the Symfony docs, in job interviews, in conversations between devs. This book is where they come from, and I wanted to see the source.

The read surprised me twice. The dictionary half is genuinely dry: C++ from 1994, vanished technologies; my eyes slid off entire pages, and I own that. But the prose half (chapter 1, the Lexi editor, the conclusion) is far better than its reputation: clear, humble, and full of warnings against the very pattern fever it launched itself.

What I take away, at my level: not the 23 patterns. I will not remember them all, and neither will you. What stays is the two principles, the question "what is going to change?", and above all the names. When I tell an AI assistant "extract this into a Strategy", it works on the first try, because the name compresses a whole conversation. And part of the catalog died honorably: Iterator lives inside every language's foreach, and saying "Singleton" in a job interview has become a risk sport.

My honest advice: read chapter 1 and Lexi like a book, and keep the rest as a dictionary you consult when needed. And if you only read one of the two, pick HFDP, the illustrated textbook: it is built for learning. This one is the stern grandfather you visit to check the source.

Odilon

Still relevant in 2026?

The vocabulary is more alive than the book: it is the shared API between developers, interviews, frameworks docs, and now AI assistants, which were trained on thirty years of text written in GoF. Telling an agent "make this a Decorator" beats three paragraphs of description. What has aged: the C++/Smalltalk code, the patterns absorbed by languages (Iterator, partly Observer via reactive primitives), and the class-heavy style; functional composition solves some of these problems with less ceremony. The two principles and the intent-based reading have not moved an inch.

Who is it for?

Read it if

  • You use the words Factory, Observer or Singleton daily and want to drink at the source
  • You design OO code (PHP, Java, C#, TypeScript) and inheritance keeps hurting you
  • You read Head First Design Patterns and want the precise, complete reference
  • You review AI-generated code: naming the structure is the fastest feedback there is

Skip it if

  • You expect a page-turner: half of it is a reference to consult, in 1994 C++
  • You live in functional or procedural code: composition of functions replaces half the catalog
  • You are starting OOP: begin with Head First Design Patterns, come back here later

Going further

The patterns are practiced in PHP in my PHP OOP course. In the library, Head First Design Patterns retells the same catalog with jokes and quizzes, PHP 8 Objects & Patterns applies it to PHP, Clean Architecture builds its Dependency Rule on the same polymorphism, and Refactoring gives the safe steps to move toward (or away from) a pattern.

Comments (0)

Browse the whole library

More book notes coming: one book at a time, the marrow only.