Chain of Responsibility Design Pattern in Java
The Chain of Responsibility Design Pattern enables decoupling between senders and receivers by allowing multiple objects to handle a request. The core idea is that a request is passed along a chain of potential handler objects. Each handler decides either to process the request or pass it to the next handler in the chain.
The sender of the request is unaware of which object in the chain will ultimately handle the request, ensuring that the sender and receiver operate independently.
Why Java Programmers Should Study the Chain of Responsibility Pattern
- Request Handling – Simplifies request processing by allowing multiple objects to handle a request dynamically.
- Decoupling Logic – Separates sender and receiver, improving maintainability and flexibility in evolving Java applications.
- Dynamic Responsibility – Enables defining or modifying the chain of responsibility at runtime.
- Error Handling – Useful for building robust error-handling chains where multiple modules can validate or log issues sequentially.
- Command Validation – Allows commands or user inputs to be validated through a series of validation handlers.
- Scalable Workflows – New handlers can be added or removed without modifying existing logic.
- Middleware Simulation – Simulates middleware behavior such as authentication, logging, and rate limiting in server-side Java applications.
Participants (for Java Students)
Handler
- Defines the interface for handling requests.
- Stores a reference to the next handler.
- Forwards requests if it cannot handle them.
ConcreteHandler
- Handles requests it is responsible for.
- Decides whether to process or forward.
- Passes unhandled requests to the successor.
Client
- Sends requests to the first handler.
- Does not know which handler processes the request.
- Relies on the chain to handle requests.
Java Example — Sandwich Maker Chain
Imagine a chain of sandwich makers. Each maker specializes in adding one specific ingredient. If they don’t have that ingredient, they pass the sandwich down the line to the next expert.
SandwichMaker.java
public abstract class SandwichMaker {
protected SandwichMaker nextMaker;
public void setNextMaker(SandwichMaker nextMaker) {
this.nextMaker = nextMaker;
}
public abstract void addIngredient(String ingredient);
}
BreadMaker.java
public class BreadMaker extends SandwichMaker {
@Override
public void addIngredient(String ingredient) {
if (ingredient.equals("bread")) {
System.out.println("BreadMaker: Toasting the bread. Nice and crispy!");
} else {
System.out.println("BreadMaker: I only deal with bread. Passing on...");
if (nextMaker != null) nextMaker.addIngredient(ingredient);
}
}
}
LettuceAdder.java
public class LettuceAdder extends SandwichMaker {
@Override
public void addIngredient(String ingredient) {
if (ingredient.equals("lettuce")) {
System.out.println("LettuceAdder: Adding fresh green lettuce. Crunchy!");
} else {
System.out.println("LettuceAdder: Lettuce only, buddy. Next please...");
if (nextMaker != null) nextMaker.addIngredient(ingredient);
}
}
}
CheeseSpreader.java
public class CheeseSpreader extends SandwichMaker {
@Override
public void addIngredient(String ingredient) {
if (ingredient.equals("cheese")) {
System.out.println("CheeseSpreader: Adding a thick slice of cheese. Yummy!");
} else {
System.out.println("CheeseSpreader: Cheese, please! Handing off...");
if (nextMaker != null) nextMaker.addIngredient(ingredient);
}
}
}
Main.java
public class Main {
public static void main(String[] args) {
SandwichMaker breadMaker = new BreadMaker();
SandwichMaker lettuceAdder = new LettuceAdder();
SandwichMaker cheeseSpreader = new CheeseSpreader();
breadMaker.setNextMaker(lettuceAdder);
lettuceAdder.setNextMaker(cheeseSpreader);
System.out.println("Making a sandwich with bread:\n");
breadMaker.addIngredient("bread");
System.out.println("\nMaking a sandwich with lettuce:\n");
breadMaker.addIngredient("lettuce");
System.out.println("\nMaking a sandwich with pickles:\n");
breadMaker.addIngredient("pickles");
}
}
Example Output
Making a sandwich with bread:
BreadMaker: Toasting the bread. Nice and crispy!
Making a sandwich with lettuce:
BreadMaker: I only deal with bread. Passing on...
LettuceAdder: Adding fresh green lettuce. Crunchy!
Making a sandwich with pickles:
BreadMaker: I only deal with bread. Passing on...
LettuceAdder: Lettuce only, buddy. Next please...
CheeseSpreader: Cheese, please! Handing off...
In this amusing sandwich shop, the BreadMaker only toasts bread, the LettuceAdder specializes in crunchy greens, and the CheeseSpreader focuses on that delicious cheese slice. If you ask for pickles though, the sandwich just travels through the chain with everyone passing it along, puzzled!
S.W.O.T. Analysis — Chain of Responsibility Pattern
Strengths
- Dynamic Workflow – Supports dynamic and flexible request-handling pipelines.
- Extensibility – Easily extended by adding new handlers.
- Decoupling – Clean separation between senders and receivers.
Weaknesses
- Execution Overhead – Long chains may reduce performance.
- Complex Debugging – Tracing requests through many handlers can be difficult.
- Order Dependency – Incorrect ordering of handlers may cause unexpected behavior.
Opportunities
- Middleware Pipelines – Useful for building middleware in Java frameworks.
- Event Handling – Effective for GUI and event-based systems.
- Authorization Chains – Ideal for multi-step authorization systems.
Threats
- Scalability Risks – Extremely long chains may impact scalability.
- Mismanagement – Poor chain management may introduce unexpected failures.
- Alternative Patterns – In some cases, patterns like State or Observer may be more appropriate.

No comments:
Post a Comment