Skip to content
GitHubLinkedin

Design Patterns: An Architectural Overview

Engineering PracticesDesign Patterns • Part 1
#design-patterns#architecture#software-design

Design patterns represent battle-tested solutions to recurring problems in software engineering. Rather than ready-made code libraries, they provide architectural blueprints for writing decoupled, maintainable, and extensible code.

Explore the Deep-Dive Guides:

  • 🏗️ Part 2: Creational Patterns — Object creation mechanisms, lifecycle control, and instantiation decoupling.
  • 🧱 Part 3: Structural Patterns — Composition over inheritance, structural boundaries, and interface adaptation.
  • 🔄 Part 4: Behavioral Patterns — Algorithms, state management, and communication patterns between objects.

Foundational Principles: SOLID

Before applying any design pattern, software architectures rely on core object-oriented principles. The SOLID principles form the foundation that most design patterns exist to preserve:

S — Single-Responsibility Principle (SRP)

"A class should have one, and only one, reason to change."

Every class or module should encapsulate a single responsibility or business actor. If a class handles database persistence, payload serialization, and business validation, a change in any of those three concerns forces modifications to the same file, increasing regression risk.

O — Open-Closed Principle (OCP)

"Software entities should be open for extension, but closed for modification."

You should be able to introduce new behavior or features without altering existing, tested code. This is typically achieved via polymorphism, interfaces, and abstractions (e.g., adding a new PaymentGateway implementation without modifying the checkout orchestrator).

L — Liskov Substitution Principle (LSP)

"Subtypes must be substitutable for their base types without altering program correctness."

If class $S$ is a subtype of $T$, objects of type $T$ should be replaceable with objects of type $S$ without breaking client expectations, throwing unexpected exceptions, or violating preconditions/postconditions.

I — Interface Segregation Principle (ISP)

"Clients should not be forced to depend upon interfaces that they do not use."

Prefer small, focused, role-specific interfaces over large, monolithic ones. Rather than creating a single Worker interface with code(), test(), deploy(), and design(), split it into specific capabilities (Developer, Tester, Deployer).

D — Dependency Inversion Principle (DIP)

"High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details; details should depend on abstractions."

Rather than having a business service directly instantiate concrete database drivers or network clients, the service depends on an interface, and the concrete implementation is injected from the outside (Inversion of Control).


The Three Design Pattern Categories

In the classic 1994 book Design Patterns: Elements of Reusable Object-Oriented Software by the "Gang of Four" (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides), the 23 canonical patterns are categorized into three distinct families based on their architectural intent:

                      Software Design Patterns
                                 │
         ┌───────────────────────┼───────────────────────┐
         ▼                       ▼                       ▼
   [ Creational ]          [ Structural ]          [ Behavioral ]
   Object creation &       Object composition &    Object communication &
   instantiation logic     class hierarchies       responsibility assignment

1. Creational Patterns

Creational patterns abstract the instantiation process. They decouple the client code from the specific classes instantiated, delegating decisions about what gets created, who creates it, how it is assembled, and when it is initialized.

Core Problems Addressed:

  • Eliminating hard-coded constructor calls (new ConcreteClass()) scattered throughout business logic.
  • Building complex objects that require multi-step assembly or cross-field validation.
  • Enforcing lifecycle boundaries (e.g., single global instances or cloned pre-configured prototypes).

Patterns in this Category:

  • Factory Method: Relies on inheritance; derived classes decide which concrete product to instantiate.
  • Abstract Factory: Relies on composition; produces families of interdependent products without specifying concrete classes.
  • Builder: Separates the incremental construction of a complex object from its final representation.
  • Prototype: Duplicates existing instances (cloning) rather than constructing new ones from scratch.
  • Singleton: Ensures a class has only one instance and provides a global access point to it.

👉 Read the comprehensive deep dive:
Creational Design Patterns: In-depth Technical Guide


2. Structural Patterns

Structural patterns focus on how classes and objects are composed to form larger, more capable structures while keeping those structures flexible and easily evolvable. They emphasize composition over inheritance.

Core Problems Addressed:

  • Integrating incompatible legacy interfaces or third-party APIs into existing codebases.
  • Preventing exponential subclass explosions when abstractions and platform implementations vary independently.
  • Adding cross-cutting responsibilities (caching, logging, access control, encryption) dynamically without modifying underlying classes.
  • Managing massive collections of fine-grained objects to avoid memory bloat.

Patterns in this Category:

  • Adapter: Translates an incompatible interface into an interface clients expect.
  • Bridge: Decouples an abstraction hierarchy from its implementation hierarchy so both can vary independently.
  • Composite: Treats individual objects (leaves) and composite trees (containers) uniformly.
  • Decorator: Attaches additional responsibilities dynamically by recursively wrapping objects sharing the same interface.
  • Facade: Provides a simplified, high-level doorway to a complex subsystem.
  • Flyweight: Reduces memory footprint by sharing intrinsic, immutable state across large numbers of objects.
  • Proxy: Provides a placeholder or surrogate object to control access, lazy-load resources, or intercept method calls.

👉 Read the comprehensive deep dive:
Structural Design Patterns: In-depth Technical Guide


3. Behavioral Patterns

Behavioral patterns are concerned with algorithms and the assignment of responsibilities between objects. They characterize complex control flows, state transitions, and patterns of communication that are difficult to trace at runtime.

Core Problems Addressed:

  • Decoupling the sender of a request from the receivers that execute it.
  • Swapping algorithms, policies, or pricing formulas dynamically without conditional statements.
  • Propagating state updates across multiple listeners in real time without hard coupling.
  • Managing multi-stage state machines cleanly without massive switch statements.
  • Recording checkpoints and snapshots for undo/redo workflows without violating encapsulation.

Patterns in this Category:

  • Strategy: Encapsulates a family of algorithms into interchangeable classes (or first-class functions).
  • Observer: One-to-many publish/subscribe broadcast notification mechanism.
  • Command: Encapsulates an operation as an object, enabling queuing, logging, and undo/redo.
  • Chain of Responsibility: Passes requests down a chain of handlers until one handles it or the pipeline short-circuits.
  • State: Alters an object's behavior when its internal state changes, avoiding tangled conditional logic.
  • Template Method: Defines the skeleton of an algorithm in a base class, letting subclasses override specific steps.
  • Iterator: Sequentially accesses aggregate collection elements without exposing internal node representations.
  • Mediator: Centralizes complex $M \times N$ object interactions into a single coordinator hub.
  • Memento: Captures and externalizes an object's internal state for later restoration.
  • Visitor: Adds new operations to a class hierarchy without modifying existing classes (Double Dispatch).

👉 Read the comprehensive deep dive:
Behavioral Design Patterns: In-depth Technical Guide


Architecture Map: Which Pattern to Use When

Problem AreaWhat You Want to DoCategoryRecommended Pattern
Object LifecycleCreate product families that must stay compatibleCreationalAbstract Factory
Construct an object with many optional parameters safelyCreationalBuilder
Avoid expensive re-initialization by duplicating an existing instanceCreationalPrototype
Share a single managed service instance across the systemCreationalSingleton (Container Scope)
Class CompositionMake incompatible APIs or libraries work togetherStructuralAdapter
Add logging, metric tracking, or caching to an existing service transparentlyStructuralDecorator or Proxy
Provide a clean 80/20 public interface to a sprawling subsystemStructuralFacade
Represent hierarchical trees (e.g., UI trees, file directories) uniformlyStructuralComposite
Render millions of small objects without exhausting RAMStructuralFlyweight
Control & CommunicationSwap business logic or calculations at runtimeBehavioralStrategy
Broadcast events to multiple subscribers asynchronouslyBehavioralObserver
Implement transactional actions, undo buffers, or task queuesBehavioralCommand
Process requests through sequential validation and auth filtersBehavioralChain of Responsibility
Manage complex object state transitions cleanlyBehavioralState

Continue exploring each category in depth:

  1. Creational Design Patterns
  2. Structural Design Patterns
  3. Behavioral Design Patterns