upgrade
upgrade

💻Design Strategy and Software I

Essential Design Patterns in Software Engineering

Study smarter with Fiveable

Get study guides, practice questions, and cheatsheets for all your subjects. Join 500,000+ students with a 96% pass rate.

Get Started

Why This Matters

Design patterns are the vocabulary of professional software development—and they're exactly what separates code that works from code that's maintainable, extensible, and elegant. You're being tested not just on what each pattern does, but on when to apply it and what problem it solves. Understanding patterns means recognizing recurring design challenges: object creation complexity, interface incompatibility, behavioral flexibility, and structural organization.

Think of patterns as proven solutions to problems developers encounter repeatedly. The Gang of Four (GoF) organized these into three categories—creational, structural, and behavioral—and that classification tells you everything about a pattern's purpose. Don't just memorize definitions; know which principle each pattern embodies (single responsibility, open-closed, dependency inversion) and be ready to compare patterns that solve similar problems in different ways.


Creational Patterns: Controlling Object Instantiation

Creational patterns abstract the instantiation process, giving you flexibility in what gets created, who creates it, and how it's created. These patterns become essential when direct construction (using new) creates coupling or complexity you want to avoid.

Singleton Pattern

  • Ensures exactly one instance of a class exists globally—critical for shared resources like configuration managers, logging services, or database connection pools
  • Provides a global access point through a static method, typically getInstance(), that returns the single instance
  • Prevents inconsistent state that would occur if multiple instances competed for the same resource or stored conflicting data

Factory Method Pattern

  • Defines an interface for object creation while letting subclasses decide which concrete class to instantiate—the "virtual constructor"
  • Promotes loose coupling by eliminating hard-coded class names; client code works with abstractions, not implementations
  • Enables extensibility since adding new product types requires only creating new subclasses, not modifying existing code

Abstract Factory Pattern

  • Creates families of related objects without specifying concrete classes—think UI toolkits that produce matching buttons, scrollbars, and windows
  • Enforces consistency across product families; you can't accidentally mix a Windows button with a Mac scrollbar
  • Supports easy theme-switching by swapping the entire factory, making it ideal for cross-platform applications

Builder Pattern

  • Separates construction from representation by extracting object creation into a dedicated builder class with step-by-step methods
  • Handles complex objects with many optional parameters—instead of telescoping constructors, use setName().setAge().setAddress().build()
  • Improves readability by making construction code self-documenting and allowing different builders to create different representations

Prototype Pattern

  • Creates objects by cloning an existing prototype rather than instantiating from scratch—copy, then customize
  • Optimizes performance when object creation is expensive (database lookups, complex calculations, heavy initialization)
  • Supports dynamic configuration since prototypes can be registered and retrieved at runtime without knowing concrete classes

Compare: Factory Method vs. Abstract Factory—both delegate object creation, but Factory Method uses inheritance (subclasses override a method) while Abstract Factory uses composition (inject a factory object). If an exam question involves creating families of related objects, Abstract Factory is your answer.

Compare: Builder vs. Prototype—Builder constructs objects step-by-step for complex initialization, while Prototype copies existing objects for performance optimization. Choose Builder when construction logic is complicated; choose Prototype when you need many similar objects quickly.


Structural Patterns: Composing Classes and Objects

Structural patterns focus on how classes and objects are composed to form larger structures. They use inheritance and composition to create flexible, efficient architectures that can adapt to changing requirements.

Adapter Pattern

  • Converts incompatible interfaces by wrapping an existing class with a new interface that clients expect—the translator between old and new
  • Enables legacy integration without modifying original source code, making it essential for working with third-party libraries
  • Implements either through inheritance (class adapter) or composition (object adapter), with composition being more flexible

Decorator Pattern

  • Adds responsibilities dynamically by wrapping objects in decorator classes that implement the same interface
  • Provides a flexible alternative to subclassing—instead of creating CoffeeWithMilk, CoffeeWithSugar, and CoffeeWithMilkAndSugar subclasses, stack decorators
  • Adheres to Single Responsibility Principle by isolating each behavior extension in its own class

Composite Pattern

  • Represents part-whole hierarchies as tree structures where individual objects and compositions share the same interface
  • Enables uniform treatment of leaves and branches—client code calls draw() without knowing if it's a single shape or a group
  • Simplifies client code by eliminating type-checking logic; recursive operations propagate naturally through the tree

Facade Pattern

  • Provides a simplified interface to a complex subsystem, hiding implementation details behind a clean API
  • Reduces coupling between clients and subsystem classes—changes to the subsystem don't ripple through client code
  • Improves usability by offering convenient default behaviors while still allowing direct subsystem access when needed

Proxy Pattern

  • Controls access to another object by providing a surrogate that intercepts requests before forwarding them
  • Implements lazy initialization (virtual proxy), access control (protection proxy), logging (logging proxy), or caching (caching proxy)
  • Maintains the same interface as the real subject, making it transparent to clients—they don't know they're using a proxy

Compare: Adapter vs. Facade—Adapter makes one incompatible interface work with your code, while Facade simplifies access to an entire subsystem. Adapter changes an interface; Facade doesn't change anything, it just provides a convenient entry point.

Compare: Decorator vs. Proxy—both wrap objects, but Decorator adds behavior while Proxy controls access. Decorator typically allows stacking multiple wrappers; Proxy usually wraps exactly once for a specific purpose like security or lazy loading.


Behavioral Patterns: Managing Algorithms and Communication

Behavioral patterns are concerned with algorithms and the assignment of responsibilities between objects. They describe how objects interact and how responsibilities are distributed, often using delegation and composition to achieve flexibility.

Observer Pattern

  • Establishes one-to-many dependencies so that when a subject changes state, all registered observers are notified automatically
  • Enables event-driven architecture where publishers and subscribers are loosely coupled—subjects don't need to know observer types
  • Supports broadcast communication essential for UI frameworks, real-time data feeds, and the Model-View-Controller pattern

Strategy Pattern

  • Encapsulates interchangeable algorithms in separate classes, allowing runtime selection without conditional statements
  • Eliminates switch/if-else chains by replacing them with polymorphism—each strategy implements a common interface
  • Promotes Open-Closed Principle since new algorithms can be added without modifying existing code

Command Pattern

  • Encapsulates requests as objects with a common interface (typically an execute() method), decoupling sender from receiver
  • Enables undo/redo functionality by storing command history and implementing undo() methods
  • Supports queuing and logging of operations, making it ideal for transaction systems and macro recording

State Pattern

  • Allows behavior changes based on internal state by delegating state-specific behavior to separate state classes
  • Eliminates complex conditional logic that would otherwise check state before every operation
  • Makes state transitions explicit by encapsulating each state and its transitions in dedicated classes

Iterator Pattern

  • Provides sequential access to collection elements without exposing the underlying data structure
  • Supports multiple simultaneous traversals since each iterator maintains its own position independently
  • Enables polymorphic iteration where the same client code can traverse arrays, linked lists, or trees uniformly

Compare: Strategy vs. State—both use delegation to change behavior, but Strategy lets clients choose algorithms while State changes behavior automatically based on internal conditions. Strategy is about what algorithm to use; State is about when behavior should change.

Compare: Observer vs. Command—Observer handles notifications (something happened, react to it) while Command handles requests (do this specific thing). Use Observer for reactive systems; use Command when you need to parameterize, queue, or undo operations.


Quick Reference Table

ConceptBest Examples
Controlling instantiationSingleton, Factory Method, Abstract Factory
Building complex objectsBuilder, Prototype
Interface adaptationAdapter, Facade
Adding behavior dynamicallyDecorator, Proxy
Representing hierarchiesComposite
Encapsulating algorithmsStrategy, Command
Managing state and eventsObserver, State
Traversing collectionsIterator
Loose couplingFactory Method, Observer, Strategy, Command
Open-Closed PrincipleDecorator, Strategy, Observer

Self-Check Questions

  1. Both Decorator and Proxy wrap objects—what's the key difference in their intent, and when would you choose each?

  2. You're building a document editor that needs undo functionality. Which pattern is most appropriate, and what method would each command object need to implement?

  3. Compare Factory Method and Abstract Factory: which uses inheritance for object creation, and which uses composition? When would you prefer one over the other?

  4. A game needs different AI behaviors (aggressive, defensive, random) that can be swapped at runtime. Which pattern applies, and how does it eliminate conditional logic?

  5. You're integrating a legacy payment system with a new e-commerce platform, and their interfaces don't match. Which structural pattern solves this, and how does it differ from Facade?