Behavioral Design Patterns
The Strategy Pattern
The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from the clients that use it.
The Real-World Analogy: Commuting to work. You can drive, take the subway, or bike. Each is a different "strategy" to achieve the same goal (arriving at work).
public interface SortStrategy { void sort(int[] data); }
public class QuickSort implements SortStrategy { public void sort(int[] d) { ... } }
public class ShellSort implements SortStrategy { public void sort(int[] d) { ... } }
public class Sorter {
private SortStrategy strategy;
public void setStrategy(SortStrategy s) { this.strategy = s; }
public void executeSort(int[] d) { strategy.sort(d); }
}
- Kill the IF-ELSE: Strategy is the #1 tool for refactoring complex
if-elseorswitchblocks that handle different business rules or processing modes.
The Observer Pattern
The Observer Pattern defines a one-to-many dependency between objects. When the "Subject" changes state, all its "Observers" (subscribers) are notified and updated automatically.
The Real-World Analogy: YouTube Subscriptions. You subscribe to a channel (Subject). When the channel uploads a video (State Change), all subscribers (Observers) receive a push notification.
public class Newsletter {
private List<Subscriber> subs = new ArrayList<>();
public void notifySubs(String news) {
subs.forEach(s -> s.onUpdate(news));
}
}
- Modern Usage: Event-driven architectures, Message Queues (Kafka/RabbitMQ), Spring's
ApplicationEventPublisher, and Reactive Programming (RxJava).
The Template Method Pattern
The Template Method defines the "skeleton" of an algorithm in a base class, but lets subclasses override specific steps without changing the algorithm's structure.
public abstract class DataProcessor {
public final void process() { // Final ensures the skeleton is fixed
readData();
processData(); // Subclass implements this
saveData();
}
protected abstract void processData();
}
- Hollywood Principle: "Don't call us, we'll call you." The base class controls the workflow and calls the subclass methods at the appropriate time.
The Chain of Responsibility
The Chain of Responsibility allows a request to be passed along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain.
public abstract class Handler {
protected Handler next;
public void handle(Request req) {
if (canHandle(req)) doHandle(req);
else if (next != null) next.handle(req);
}
}
// Usage: FilterChain, InterceptorChain, Approval Workflows.
Comparison Matrix
| Pattern | Core Intent | Best Used When... |
|---|---|---|
| Strategy | Interchangeable logic | You have multiple ways to do the same task. |
| Observer | State synchronization | One object's change needs to trigger others. |
| Template | Fixed workflow skeleton | The steps are the same, but details differ. |
| Chain | Decoupling Sender/Receiver | Multiple objects could potentially handle a request. |
Deep Technical Insights
Strategy + Factory
In high-level architecture, the Strategy pattern is often paired with a Factory. The Factory determines which strategy instance to create (perhaps based on a config file or a database setting), and the Strategy performs the actual work. This allows you to add entire new behaviors to an app without changing a single line of the main business logic.
The Pitfalls of Observers
- Memory Leaks: If you forget to "unsubscribe," the Subject will hold a strong reference to the Observer forever, preventing Garbage Collection.
- Execution Order: Observers should never depend on being called in a specific order. If order matters, use a Chain of Responsibility or a Command pattern instead.
- Synchronous Overhead: If an Observer's
update()method is slow or blocks, it can stop the entire system. In these cases, move to asynchronous event processing (Message Queues).