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?
- Shared vocabulary: "Let's use a Singleton here" communicates design intent instantly to your team.
- Proven solutions: You avoid reinventing the wheel for problems that thousands of engineers have already solved.
- Maintainable code: Pattern-based code is structured to handle change and extension gracefully.
- Interview preparation: Design patterns are a staple topic in backend developer interviews.
2. The Three Pattern Categories
| Category | Purpose | Key Patterns |
|---|---|---|
| Creational | Encapsulate object creation logic | Singleton, Factory Method, Builder, Prototype, Abstract Factory |
| Structural | Compose classes and objects into larger structures | Adapter, Decorator, Proxy, Facade, Composite |
| Behavioral | Manage communication and responsibility between objects | Strategy, 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:
| Pattern | Java Standard Library Example |
|---|---|
| Singleton | Runtime.getRuntime(), System |
| Factory Method | Calendar.getInstance(), List.of() |
| Builder | StringBuilder, Stream.Builder, HttpClient.newBuilder() |
| Iterator | Iterator<E>, enhanced for-each loop |
| Observer | EventListener, PropertyChangeListener |
| Strategy | Comparator<T>, Runnable, Callable |
| Decorator | BufferedReader, Collections.unmodifiableList() |
| Proxy | java.lang.reflect.Proxy |
| Adapter | Arrays.asList(), InputStreamReader |
| Template Method | AbstractList, HttpServlet.doGet() |
4. SOLID Principles Review
Design patterns are applications of SOLID principles. Understanding SOLID helps you recognize why each pattern exists.
| Principle | Short Form | Meaning |
|---|---|---|
| Single Responsibility | SRP | A class should have only one reason to change |
| Open/Closed | OCP | Open for extension, closed for modification |
| Liskov Substitution | LSP | Subtypes must be substitutable for their base types |
| Interface Segregation | ISP | Clients should not depend on interfaces they don't use |
| Dependency Inversion | DIP | Depend 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:
| Pattern | Spring Usage |
|---|---|
| Singleton | Every @Component, @Service, @Repository bean |
| Factory | BeanFactory, ApplicationContext |
| Proxy | @Transactional, @Cacheable, @Aspect (AOP) |
| Template Method | JdbcTemplate, RestTemplate, KafkaTemplate |
| Observer | ApplicationEvent + @EventListener |
| Decorator | Spring Security filter chain |
| Strategy | AuthenticationProvider, 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
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.