Chain of Responsibility Pattern — A Quick Guide
Design Patterns: Chain of Responsibility Pattern
--
“Chain of Responsibility Pattern lets you pass requests 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.”
Simple Example:
Suppose you want to play a video game on your computer and the game is trying to choose the optimal settings based on your GPU.
It checks if your GPU can handle Ultra, if not;
it checks if your GPU can handle High, if not;
it checks if your GPU can handle Medium.
And if you are lucky enough, it stops there.
If your GPU still isn’t enough, it checks if you can handle Low,
then Ultra Low (if the game has this option).
If your GPU still isn’t enough, the game simply won’t work on your computer.
Chain of Responsibility Pattern is actually as simple as this.
Real-Life Example:
Up until now you must have realized this is all bunch of if & else if statements.
public void initialize(VisualEntity visualEntity,
FrameRequest frameRequest) {
initializeCommonFields(visualEntity, frameRequest);
if (frameRequest.isCircle()) {
// initialize circle
} else if (frameRequest.isSquare()) {
// initialize square
} else if (frameRequest.isRectangle()) {
// initialize rectangle
} else throw new UnsupportedOperationException("message");
}
Let’s say you have a code block like this where you let your client choose a frame for their photo.
Available paths are:
Your client chooses Circle,
Code checks if it’s circle,
Code initializes Circle.
Your client chooses Square,
Code checks if it’s circle,
Code checks if it’s square,
Code initializes Square.
Your client chooses Rectangle,
Code checks if it’s circle,
Code checks if it’s square,
Code checks if it’s rectangle,
Code initializes Rectangle.
Your client chooses Triangle (which doesn’t exist in our app),
Code checks if it’s circle,
Code checks if it’s square,
Code checks if it’s rectangle,
Code throws exception.
But I believe we can agree that all these if blocks don’t look very beautiful and if one day you want to add Triangle to your available frames, or maybe a Heart symbol for couples, the code block would get even larger.
So let’s try a different design with Chain of Responsibility!
Implementation
public abstract class InitializerSelector {
private InitializerSelector successor;
abstract void initialize(VisualEntity visualEntity,
FrameRequest frameRequest);
public InitializerSelector getSuccessor() {
return successor;
}
public void setSuccessor(InitializerSelector successor) {
this.successor = successor;
}
}
public class CircleInitializer extends InitializerSelector {
public CircleInitializer(SquareInitializer squareInitializer) {
setSuccessor(squareInitializer);
}
@Override
public void initialize(VisualEntity visualEntity,
FrameRequest frameRequest) {
if (frameRequest.isCircle()) {
// initialize circle
} else {
getSuccessor().initialize(visualEntity, frameRequest);
}
}
}
public class SquareInitializer extends InitializerSelector {
public SquareInitializer(RectangleInitializer rectangleInitializer) {
setSuccessor(rectangleInitializer);
}
@Override
public void initialize(VisualEntity visualEntity,
FrameRequest frameRequest) {
if (frameRequest.isSquare()) {
// initialize square
} else {
getSuccessor().initialize(visualEntity, frameRequest);
}
}
}
public class RectangleInitializer extends InitializerSelector {
@Override
public void initialize(VisualEntity visualEntity,
FrameRequest frameRequest) {
if (frameRequest.isRectangle()) {
// initialize rectangle
} else {
throw new UnsupportedOperationException("message");
}
}
}
If one day you need to add TriangleInitializer, you can add it as a successor of RectangleInitializer and move the Exception to TriangleInitializer.
And the client code would be like:
public void initialize(VisualEntity visualEntity,
FrameRequest frameRequest) {
initializeCommonFields(visualEntity, frameRequest);
circleInitializer.initialize(visualEntity, frameRequest);
}
You can finalize the client code by changing the initial call from circleInitializer to something more abstract, to not reveal logic to client code. You can create a new class which will start the flow by calling CircleInitializer.
public void initialize(VisualEntity visualEntity,
FrameRequest frameRequest) {
initializeCommonFields(visualEntity, frameRequest);
frameInitializer.initialize(visualEntity, frameRequest);
}
Voila!
There are many other ways to implement the Chain of Responsibility Pattern and Spring has its own built-in way to implement it even easier, feel free to use different approaches based on your situation (and framework choice).
Thank you for reading and being a part of my journey!
Reference(s)
- Dive into Design Patterns. by Alexander Shvets, Released 2019.