Chain of Responsibility Design Pattern in Rust

1. Definition

The Chain of Responsibility design pattern allows an object to pass a request along a chain of potential handlers until an object handles it or the end of the chain is reached. This decouples the sender from the receiver, allowing multiple objects to handle the request independently.

2. Problem Statement

Imagine a system where a request can be handled by several types of handlers, but only one should act upon it based on specific criteria. Instead of hardcoding the decision-making logic, how can we efficiently delegate the request through a series of handlers?

3. Solution

The Chain of Responsibility pattern lets you pass a request along a chain of handlers. Each handler decides either to process the request or to pass it along to the next handler in the chain.

4. Real-World Use Cases

1. Event handling in GUI libraries, where events can be handled by multiple components (e.g., button, dialog, window).

2. Middleware in web servers, where a request can be processed by multiple middleware functions before reaching the final handler.

3. Input validation in processing pipelines.

5. Implementation Steps

1. Define a handler interface with a method to set the next handler and another method to handle the request.

2. Create concrete handlers implementing the handler interface. Each handler decides whether to handle the request or pass it to the next handler.

3. Link handlers to form a chain.

6. Implementation in Rust Programming

// Abstract handler
trait Handler {
    fn set_next(&mut self, next: Box<dyn Handler>);
    fn handle(&self, request: &str);
}
// Concrete handler 1
struct Handler1 {
    next: Option<Box<dyn Handler>>,
}
impl Handler1 {
    pub fn new() -> Self {
        Handler1 { next: None }
    }
}
impl Handler for Handler1 {
    fn set_next(&mut self, next: Box<dyn Handler>) {
        self.next = Some(next);
    }
    fn handle(&self, request: &str) {
        if request == "Request1" {
            println!("Handler1: Handling {}", request);
        } else if let Some(next) = &self.next {
            next.handle(request);
        }
    }
}
// Concrete handler 2
struct Handler2 {
    next: Option<Box<dyn Handler>>,
}
impl Handler2 {
    pub fn new() -> Self {
        Handler2 { next: None }
    }
}
impl Handler for Handler2 {
    fn set_next(&mut self, next: Box<dyn Handler>) {
        self.next = Some(next);
    }
    fn handle(&self, request: &str) {
        if request == "Request2" {
            println!("Handler2: Handling {}", request);
        } else if let Some(next) = &self.next {
            next.handle(request);
        }
    }
}
// Client code
fn main() {
    let mut handler1 = Handler1::new();
    let handler2 = Handler2::new();
    handler1.set_next(Box::new(handler2));
    handler1.handle("Request1");
    handler1.handle("Request2");
}

Output:

"Handler1: Handling Request1"
"Handler2: Handling Request2"

Explanation:

1. Handler is the common interface for all concrete handlers.

2. Handler1 and Handler2 are concrete handlers that either handle a request or pass it to the next handler.

3. In the client code, we set up a chain of handlers. Requests passed to the first handler either get processed or are passed down the chain.

7. When to use?

The Chain of Responsibility pattern is useful when:

1. More than one object might handle a request, and the handler isn't known a priori. The handler should be determined automatically.

2. You want to issue a request to multiple objects without specifying the receiver explicitly.

3. The set of objects that can handle a request should be specified dynamically.


Comments