Chain of Responsibility is far more flexible than a case statement. Importantly, CoR can:
process the requests without hard-wiring handler relationships and
precedence, or request-to-handler mappings.
Meaning clients are unaware of any subsequent handlers, or even the existence of a chain.
The number and type of handler objects isn't known a priori, they can
be configured dynamically.
Meaning new handlers can be added at runtime, and existing handlers can be reordered.
A more basic answer is that case statements are a procedural construct, and therefore are generally not used in object-oriented programming, such as the Gang of Four design patterns.
Online examples may tend to configure a CoR within the client for the sake of simplicity; but in practice that defeats the purpose of the pattern, so the CoR would be configured elsewhere. Toy examples merely aim to show what a chain looks like and how it operates after instantiation; but where it is instantiated is key to the motivation for choosing CoR.
Example: A client depends on a service to process a String value.
The service API is trivial.
interface StringHandler {
void handle(String arg);
}
The client may be infinitely complex, but at some point it invokes the service.
class Client {
private final StringHandler argHandler;
Client(StringHandler argHandler) {
this.argHandler = argHandler;
}
void method(String arg) {
argHandler.handle(arg);
// more business logic...
}
}
We choose to implement the service as a Chain of Responsibility.
class ChainedHandler implements StringHandler {
private final String handledString;
private ChainedHandler next;
ChainedHandler(String handledString) {
this.handledString = handledString;
}
Optional<ChainedHandler> next() {
return Optional.ofNullable(next);
}
ChainedHandler next(ChainedHandler handler) {
ChainedHandler subsequent = next;
next = handler;
if (handler != null && subsequent != null)
handler.next(subsequent);
return this;
}
@Override
public void handle(String arg) {
if (arg.equalsIgnoreCase(handledString)) {
System.out.println("Handled: " + arg);
} else {
next().ifPresentOrElse(
handler -> handler.handle(arg),
() -> System.out.println("No handler for: " + arg));
}
}
}
So we build a Chain, wire it into the Client, and execute a few scenarios by modifying the Chain.
public static void main(String... commandLineArgs) {
List<String> args = commandLineArgs.length > 0
? Arrays.asList(commandLineArgs)
: List.of("foo", "bar", "baz", "qux");
ChainedHandler chain = new ChainedHandler("foo")
.next(new ChainedHandler("bar")
.next(new ChainedHandler("baz")));
Client client = new Client(chain);
args.forEach(client::method);
System.out.println();
chain.next(new ChainedHandler("qux"));
args.forEach(client::method);
System.out.println();
chain.next(null);
args.forEach(client::method);
}
Note the Client
is unaware that a chain exists. Furthermore, note the chain is modified without editing code. This is the decoupling that the GoF refers to. A case statement or if/else block would not provide the same flexibility.