user3393354
user3393354

Reputation: 31

Chain of Responsibility VS Case statements

When I read about Chain of Responsibility, it talks about separating the client from the actual processing of the data. So it says the client program calls the first member of a chain and that member determines if it can process the request and if it can, it processes it and if not it calls the next member of the chain and so on.

I have read that additional members can be added to the chain and the client program does not have to actually change to handle that. What I am seeing in examples, including Wikipedia, is that the client has to instantiate each member of the chain and even set the successor for each member of the chain.

How is this considered loose coupling when the order of the objects in the chain is determined by the client and the client even has to instantiate each object in the chain?

Upvotes: 3

Views: 1834

Answers (1)

jaco0646
jaco0646

Reputation: 17104

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.

Upvotes: 3

Related Questions