askesis

Design patterns basics: 50 cards on SOLID and the Gang of Four

SOLID, DRY, KISS and YAGNI, plus the Gang of Four patterns used most: singleton, factory, builder, adapter, decorator, observer, strategy, command and more.

Computer scienceIntermediate50 cardsFree

Practice now

Study without an account

0 of 50 seen0 graduated
What does SOLID stand for?

Scheduled with the default Askesis timings. Progress is saved in this browser.

All 50 cards

  1. 1
    What does SOLID stand for?
    Single responsibility, Open/closed, Liskov substitution, Interface segregation, Dependency inversion
    Five object-oriented design principles popularized by Robert C. Martin.
  2. 2
    Single responsibility principle
    A class or module should have one reason to change: one job, serving one part of the business
    An Invoice class that calculates totals, renders PDFs and sends emails has three reasons to change.
  3. 3
    Open/closed principle
    Code should be open for extension but closed for modification: add behavior by adding code, such as a new class or strategy, instead of editing code that works
    A switch on a type that you edit for every new case is the classic violation.
  4. 4
    Liskov substitution principle
    Objects of a subtype must work anywhere the base type is expected, without surprising the caller
    Classic violation: Square extends Rectangle. Setting a square’s width also changes its height, which breaks code that expects a rectangle’s sides to vary independently.
  5. 5
    Interface segregation principle
    Clients shouldn’t depend on methods they don’t use, so prefer several small, focused interfaces over one large one
    A Printer shouldn’t have to implement fax() just because it shares a Machine interface with a fax machine.
  6. 6
    Dependency inversion principle
    High-level code should depend on abstractions, not on concrete low-level details; the details implement those abstractions
    An OrderService depends on a PaymentGateway interface, not directly on a StripeClient.
  7. 7
    DRY
    “Don’t repeat yourself”: every piece of knowledge should have a single, authoritative representation in the system
    It’s about duplicated knowledge, not code that merely looks similar. Merging look-alike code that changes for different reasons creates the wrong abstraction.
  8. 8
    KISS
    “Keep it simple, stupid”: prefer the simplest design that solves the problem
  9. 9
    YAGNI
    “You aren’t gonna need it”: don’t build features or flexibility until they’re actually needed
    From Extreme Programming. Speculative generality costs time now and complexity forever.
  10. 10
    Composition over inheritance
    Build behavior by combining objects that each do one thing (has-a) instead of extending class hierarchies (is-a)
    Composed parts can be swapped at runtime, and you avoid fragile base classes and deep hierarchies.
  11. 11
    Law of Demeter
    A method should talk only to its immediate collaborators, not reach through them: avoid chains like order.getCustomer().getAddress().getCity()
    Also called the principle of least knowledge.
  12. 12
    Coupling vs cohesion
    Coupling is how much modules depend on each other; cohesion is how closely the parts of one module belong together. Aim for low coupling and high cohesion
  13. 13
    What is a design pattern?
    A named, reusable solution to a problem that keeps recurring in software design: a template to adapt, not finished code
  14. 14
    The three categories of Gang of Four patterns
    Creational (how objects are created), structural (how objects are composed), behavioral (how objects interact and share responsibilities)
    From “Design Patterns” (1994) by Gamma, Helm, Johnson and Vlissides, which catalogs 23 patterns.
  15. 15
    Singleton
    Ensures a class has only one instance and provides a global point of access to it
    Often considered an anti-pattern: it’s global state that hides dependencies and makes testing hard. Creating one instance and passing it in is usually better.
  16. 16
    Which pattern is this?
    typescript
    class Config {
      private static instance?: Config
      private constructor() {}
    
      static get(): Config {
        return (Config.instance ??= new Config())
      }
    }
    Singleton: the private constructor blocks new Config() from outside, and Config.get() always returns the same instance
  17. 17
    Factory method
    Creates objects through a method instead of direct constructor calls, so a subclass or configuration decides which concrete class to instantiate
    Callers depend only on the product’s interface, not on the concrete classes.
  18. 18
    Abstract factory
    An interface for creating families of related objects without naming their concrete classes
    A UIFactory with createButton() and createCheckbox(), implemented once for Windows and once for macOS.
  19. 19
    Builder
    Constructs a complex object step by step, often through a fluent API that ends with build()
    java
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://example.com"))
        .header("Accept", "application/json")
        .GET()
        .build();
  20. 20
    Prototype
    Creates new objects by copying an existing instance instead of building them from scratch
    Useful when objects are expensive to set up, or when you don’t know their concrete class. In JavaScript, structuredClone(obj) makes a deep copy.
  21. 21
    Adapter
    Wraps an object to convert its interface into the one clients expect, so classes with incompatible interfaces can work together
    Like a travel plug adapter: the same electricity through a different shape.
  22. 22
    Decorator
    Wraps an object in another object with the same interface that adds behavior, without changing the original class; wrappers can be stacked
    Java I/O is built this way: new BufferedReader(new FileReader("data.txt")) adds buffering to any Reader.
  23. 23
    Facade
    A simple interface in front of a complex subsystem, e.g. one VideoConverter.convert(file, "mp4") call that hides codecs, buffers and containers
    Clients can still use the subsystem directly when they need more control.
  24. 24
    Proxy
    A stand-in with the same interface as the real object that controls access to it: lazy loading, permission checks, caching or calling a remote service
    JavaScript’s built-in Proxy lets you intercept property access on any object.
  25. 25
    Composite
    Composes objects into tree structures so clients can treat single items and groups of items the same way
    A UI Panel holds buttons and other panels; calling render() on a panel renders all of its children.
  26. 26
    Adapter vs decorator vs proxy vs facade
    An adapter changes an interface; a decorator keeps it and adds behavior; a proxy keeps it and controls access; a facade puts one simpler interface over many
  27. 27
    Observer
    A subject keeps a list of observers and notifies all of them when its state changes (one-to-many)
    typescript
    // DOM events are observers: the button notifies both listeners
    button.addEventListener("click", () => save())
    button.addEventListener("click", () => track("save"))
  28. 28
    Strategy
    Defines a family of interchangeable algorithms behind one interface, so the algorithm can be chosen at runtime
    With first-class functions, a strategy is often just a function, like the comparator in items.sort((a, b) => a.price - b.price).
  29. 29
    Command
    Wraps a request, the action and its parameters, in an object so it can be queued, logged, retried or undone
  30. 30
    Template method
    A base class defines the skeleton of an algorithm and lets subclasses override specific steps without changing its overall structure
  31. 31
    Iterator
    Gives sequential access to the elements of a collection without exposing how it’s stored
    Built into most languages: for...of with Symbol.iterator in JavaScript, __iter__ and __next__ in Python.
  32. 32
    State
    An object delegates its behavior to a state object and swaps that object when its state changes, so it seems to change class
    It replaces large switch statements on a status field, e.g. an order that is pending, paid, shipped or cancelled.
  33. 33
    Chain of responsibility
    Passes a request along a chain of handlers; each one either handles it or passes it on to the next
    Web framework middleware for authentication, logging and rate limiting works this way.
  34. 34
    Strategy vs state
    Both delegate to swappable objects. With strategy, the client picks the algorithm and the strategies don’t know each other; with state, the states themselves switch to the next state as things change
  35. 35
    Dependency injection
    An object receives its dependencies from outside, usually through its constructor, instead of creating them itself
    typescript
    class OrderService {
      constructor(private payments: PaymentGateway) {}
    }
    
    new OrderService(new StripeGateway()) // production
    new OrderService(new FakeGateway())   // tests
  36. 36
    Dependency inversion vs dependency injection vs IoC container
    Dependency inversion is the principle (depend on abstractions); dependency injection is the technique (pass dependencies in); an IoC container is a library that wires dependencies together automatically
  37. 37
    Why is a service locator often called an anti-pattern?
    Classes pull their dependencies from a global registry (Locator.get(Mailer)), so the dependencies are hidden instead of listed in the constructor, which makes them harder to see, swap and test
  38. 38
    God object
    An anti-pattern where one class knows or does far too much and attracts every unrelated change
    Split it along its responsibilities (single responsibility principle).
  39. 39
    When shouldn’t you use a design pattern?
    When the problem it solves isn’t there yet. Patterns add indirection, and forcing them in makes simple code harder to read
    Start simple, and refactor toward a pattern when duplication or change actually calls for it.
  40. 40
    When an order is placed, email, SMS and analytics must react, without the order code knowing about them
    Pick a pattern
    Observer (or publish-subscribe): the order publishes an event, and each service subscribes to it
  41. 41
    A constructor has a dozen optional parameters
    Pick a pattern
    Builder: set only the options you need, then call build()
  42. 42
    Your code expects a PaymentGateway interface, but the vendor’s SDK has different method names
    Pick a pattern
    Adapter: wrap the SDK in a class that implements PaymentGateway
  43. 43
    Add caching and logging around a repository without changing its class
    Pick a pattern
    Decorator: wrap the repository in objects with the same interface, each adding one behavior
  44. 44
    Shipping cost is calculated differently for each carrier, chosen at checkout
    Pick a pattern
    Strategy: one ShippingCalculator interface, with one implementation per carrier
  45. 45
    A text editor needs undo and redo
    Pick a pattern
    Command: each edit is an object with execute() and undo(), kept on undo and redo stacks
  46. 46
    Give callers one placeOrder() method that coordinates the inventory, payment and shipping subsystems
    Pick a pattern
    Facade: a single entry point that hides how the subsystems work together
  47. 47
    Create a CsvParser, JsonParser or XmlParser based on a file extension known only at runtime
    Pick a pattern
    Factory: a createParser(ext) method returns the right implementation behind a common Parser interface
  48. 48
    Load a large image from disk only when it’s first displayed
    Pick a pattern
    Proxy (a virtual proxy): a lightweight stand-in loads the real image on first use
  49. 49
    A vending machine behaves differently when idle, when holding coins and when sold out
    Pick a pattern
    State: one object per state, each handling input and switching the machine to the next state
  50. 50
    Folders contain files and other folders, and you need the total size of any of them
    Pick a pattern
    Composite: File and Folder share a size() method, and a folder sums the sizes of its children

Frequently asked questions

Do I need to know all 23 Gang of Four patterns?

No. About a dozen, such as factory, builder, adapter, decorator, facade, observer, strategy and command, cover most real code. The rest are worth recognizing when you meet them.

Which language are the examples in?

TypeScript, with a couple of Java snippets. The patterns are language-independent, and many get simpler in languages with first-class functions.

Is the singleton pattern bad?

Not always, but it is global state in disguise: it hides dependencies and complicates tests. Creating one instance at startup and passing it where it is needed gives the same benefit without the drawbacks.