The Chain of Responsibility pattern decouples a request sender from its receivers by organizing multiple handlers into a sequential processing pipeline. This design allows requests to be dynamically routed through a series of processors until they are handled, promoting flexibility and independent reuse of both the sender and the processing components. How ever, this flexibility can introduce processing latency. If the relationship between a request and its specific handler is strictly defined and requires maximum execution speed, this pattern should be avoided.
Basic Sequential Processing
The simplest form of this patern involves a list of functional operations applied to an input sequentially. In this example, a string is passed through a series of transformation steps.
import java.util.List;
import java.util.function.Function;
public class BasicPipeline {
public static void main(String[] args) {
TextProcessor processor = new TextProcessor(List.of(
s -> s.trim(),
s -> s.substring(0, 1).toUpperCase() + s.substring(1),
s -> s + "."
));
String output = processor.execute(" hello world ");
System.out.println(output); // Outputs: Hello world.
}
static class TextProcessor {
private final List<Function<String, String>> steps;
public TextProcessor(List<Function<String, String>> steps) {
this.steps = steps;
}
public String execute(String input) {
String current = input;
for (Function<String, String>> step : steps) {
current = step.apply(current);
}
return current;
}
}
}
Composable Pipelines
To increase modularity, processing pipelines can be nested within one another. By making the pipeline itself implement the functional interface, an entire pipeline can be injected as a single step into another pipeline, similar to the Decorator pattern.
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class ComposablePipeline {
public static void main(String[] args) {
ProcessingPipeline mainPipeline = new ProcessingPipeline();
mainPipeline.addStep(s -> s.replace("error", "success"));
ProcessingPipeline subPipeline = new ProcessingPipeline(List.of(
s -> s.replace("fail", "pass"),
s -> s.toUpperCase()
));
// Injecting the subPipeline as a compositional step
mainPipeline.addStep(subPipeline);
String result = mainPipeline.process("error and fail");
System.out.println(result); // Outputs: SUCCESS AND PASS
}
static class ProcessingPipeline implements Function<String, String> {
private final List<Function<String, String>> steps;
public ProcessingPipeline() {
this.steps = new ArrayList<>();
}
public ProcessingPipeline(List<Function<String, String>> steps) {
this.steps = steps;
}
public void addStep(Function<String, String> step) {
this.steps.add(step);
}
public String process(String input) {
String current = input;
for (Function<String, String> step : steps) {
current = step.apply(current);
}
return current;
}
@Override
public String apply(String input) {
return process(input);
}
}
}
Recursive Interception (Servlet Filter Model)
A more advanced implementation mimics the Java Servlet Filter mechanism. In this model, each interceptor processes the request, invokes the next interceptor in the chain via the pipeline itself, and then processes the response on the way back. This requires maintaining a cursor to track the current position in the chain during execution.
import java.util.ArrayList;
import java.util.List;
public class InterceptorChainDemo {
public static void main(String[] args) {
InterceptorChain chain = new InterceptorChain();
chain.addInterceptor((query, result, pipeline) -> {
query.payload += ">AuthCheck";
pipeline.proceed(query, result);
result.payload += "<AuthCheck";
});
chain.addInterceptor((query, result, pipeline) -> {
query.payload += ">LogTime";
pipeline.proceed(query, result);
result.payload += "<LogTime";
});
Query q = new Query();
Result r = new Result();
chain.execute(q, r);
System.out.println("Query: " + q.payload); // Outputs: >AuthCheck>LogTime
System.out.println("Result: " + r.payload); // Outputs: <LogTime<AuthCheck
}
@FunctionalInterface
interface Interceptor {
void intercept(Query query, Result result, InterceptorChain pipeline);
}
static class InterceptorChain implements Interceptor {
private int cursor = 0;
private final List<Interceptor> interceptors = new ArrayList<>();
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
@Override
public void intercept(Query query, Result result, InterceptorChain pipeline) {
if (cursor >= interceptors.size()) return;
interceptors.get(cursor++).intercept(query, result, pipeline);
}
public void execute(Query query, Result result) {
cursor = 0;
intercept(query, result, this);
}
}
static class Query {
String payload = "";
}
static class Result {
String payload = "";
}
}