Skip to main content

Ch 17.1 Design Patterns Overview

Design patterns are proven, reusable solutions to commonly recurring problems in software design. Like architectural blueprints, they provide time-tested structures and vocabulary for solving similar problems — without prescribing the exact code.

The 23 patterns cataloged by the Gang of Four (GoF) in their 1994 book "Design Patterns: Elements of Reusable Object-Oriented Software" are the industry standard, organized into three categories.

1. Why Learn Design Patterns?

  1. Shared vocabulary: "Let's use a Singleton here" communicates design intent instantly to your team.
  2. Proven solutions: You avoid reinventing the wheel for problems that thousands of engineers have already solved.
  3. Maintainable code: Pattern-based code is structured to handle change and extension gracefully.
  4. Interview preparation: Design patterns are a staple topic in backend developer interviews.

2. The Three Pattern Categories

CategoryPurposeKey Patterns
CreationalEncapsulate object creation logicSingleton, Factory Method, Builder, Prototype, Abstract Factory
StructuralCompose classes and objects into larger structuresAdapter, Decorator, Proxy, Facade, Composite
BehavioralManage communication and responsibility between objectsStrategy, Observer, Template Method, Command, Iterator

Creational Patterns

Control how objects are created, keeping creation logic separate from the code that uses the objects.

  • Singleton: Ensure a class has exactly one instance
  • Factory Method: Let subclasses decide which class to instantiate
  • Builder: Separate the construction of a complex object from its representation
  • Prototype: Create objects by cloning an existing instance
  • Abstract Factory: Create families of related objects without specifying concrete classes

Structural Patterns

Define how classes and objects can be assembled to form larger, more complex structures.

  • Adapter: Convert one interface into another that clients expect
  • Decorator: Add responsibilities to objects dynamically without subclassing
  • Proxy: Provide a surrogate that controls access to another object
  • Facade: Provide a simplified interface to a complex subsystem
  • Composite: Compose objects into tree structures to represent part-whole hierarchies

Behavioral Patterns

Focus on algorithms and the assignment of responsibilities between objects.

  • Strategy: Define a family of algorithms and make them interchangeable
  • Observer: Notify all dependent objects automatically when state changes
  • Template Method: Define the skeleton of an algorithm, deferring some steps to subclasses
  • Command: Encapsulate a request as an object
  • Iterator: Access elements of a collection sequentially without exposing the underlying representation

3. Design Patterns in Java's Standard Library

Java's standard library is full of patterns in action:

PatternJava Standard Library Example
SingletonRuntime.getRuntime(), System
Factory MethodCalendar.getInstance(), List.of()
BuilderStringBuilder, Stream.Builder, HttpClient.newBuilder()
IteratorIterator<E>, enhanced for-each loop
ObserverEventListener, PropertyChangeListener
StrategyComparator<T>, Runnable, Callable
DecoratorBufferedReader, Collections.unmodifiableList()
Proxyjava.lang.reflect.Proxy
AdapterArrays.asList(), InputStreamReader
Template MethodAbstractList, HttpServlet.doGet()

4. SOLID Principles Review

Design patterns are applications of SOLID principles. Understanding SOLID helps you recognize why each pattern exists.

PrincipleShort FormMeaning
Single ResponsibilitySRPA class should have only one reason to change
Open/ClosedOCPOpen for extension, closed for modification
Liskov SubstitutionLSPSubtypes must be substitutable for their base types
Interface SegregationISPClients should not depend on interfaces they don't use
Dependency InversionDIPDepend on abstractions, not concrete implementations
// Without Strategy pattern (violates OCP — adding new payment requires modifying code)
void pay(String type, int amount) {
if (type.equals("credit")) { /* credit card logic */ }
else if (type.equals("paypal")) { /* PayPal logic */ }
// adding crypto? modify this method!
}

// With Strategy pattern (OCP-compliant — add new payment without modifying existing code)
interface PaymentStrategy { void pay(int amount); }

class CreditCard implements PaymentStrategy { /* ... */ }
class PayPal implements PaymentStrategy { /* ... */ }
class Crypto implements PaymentStrategy { /* ... */ } // added without touching others

5. Top 5 Most Important Patterns for Java Developers

1. Singleton — Global Instance

Used for: database connection pools, loggers, configuration managers, caches.

public enum AppConfig {
INSTANCE; // thread-safe, serialization-safe singleton
private final String dbUrl = "jdbc:postgresql://localhost/mydb";
public String getDbUrl() { return dbUrl; }
}
// Usage: AppConfig.INSTANCE.getDbUrl()

2. Factory — Decouple Creation

Used for: creating objects without specifying the exact class; pluggable implementations.

interface Logger { void log(String msg); }
class FileLogger implements Logger { /* ... */ }
class ConsoleLogger implements Logger { /* ... */ }

class LoggerFactory {
public static Logger create(String type) {
return switch (type) {
case "file" -> new FileLogger();
case "console" -> new ConsoleLogger();
default -> throw new IllegalArgumentException("Unknown: " + type);
};
}
}

3. Builder — Complex Object Construction

Used for: objects with many optional fields; fluent APIs.

HttpRequest request = HttpRequest.newBuilder()         // Builder
.uri(URI.create("https://api.example.com/data"))
.header("Accept", "application/json")
.GET()
.build(); // final object

4. Strategy — Interchangeable Algorithms

Used for: payment methods, sorting algorithms, validation rules.

@FunctionalInterface
interface SortStrategy { void sort(int[] arr); }

// Swap strategies at runtime
SortStrategy strategy = arr -> Arrays.sort(arr); // built-in
strategy = arr -> bubbleSort(arr); // custom

5. Observer — Event-Driven Architecture

Used for: event listeners, pub/sub systems, Spring events.

interface EventListener { void onEvent(String event); }

class EventBus {
private final List<EventListener> listeners = new ArrayList<>();
public void subscribe(EventListener l) { listeners.add(l); }
public void publish(String event) { listeners.forEach(l -> l.onEvent(event)); }
}

6. Design Patterns in Spring Framework

Spring's architecture is built on design patterns:

PatternSpring Usage
SingletonEvery @Component, @Service, @Repository bean
FactoryBeanFactory, ApplicationContext
Proxy@Transactional, @Cacheable, @Aspect (AOP)
Template MethodJdbcTemplate, RestTemplate, KafkaTemplate
ObserverApplicationEvent + @EventListener
DecoratorSpring Security filter chain
StrategyAuthenticationProvider, MessageConverter

7. Warning: Avoid Over-Engineering

Design patterns are tools, not goals. Applying them everywhere leads to unnecessarily complex code.

// Over-engineered: unnecessary Factory for a simple object
class PointFactory {
public static Point createPoint(int x, int y) {
return new Point(x, y); // just use new Point(x, y) directly!
}
}

// Apply patterns when:
// 1. A problem matches the pattern's intent
// 2. The pattern reduces complexity (not increases it)
// 3. Future extensions are genuinely expected
tip

Pro tip: The three most impactful patterns to learn first are Strategy, Observer, and Builder— they appear constantly in modern Java frameworks. Start by recognizing them in code you already use (e.g., Comparator = Strategy, @EventListener = Observer, HttpClient.newBuilder() = Builder), then practice implementing them from scratch.