Chain of Responsibility Design Pattern in Go

1. Definition

The Chain of Responsibility Pattern decouples the sender from receiver by allowing more than one object to handle a request. It passes the request along a chain of potential handlers until an object handles it or the end of the chain is reached.

2. Problem Statement

Imagine a system where you need to process a request or event, but you have multiple objects that can potentially process it, and the specific handler isn't predetermined. Directly coupling the sender to the handlers would be inflexible and difficult to maintain.

3. Solution

Link potential handler objects in a chain, and pass the request along the chain. Each handler decides whether to process the request or pass it to the next handler in the chain.

4. Real-World Use Cases

1. Event handling in GUI libraries where multiple elements (like buttons, panels) can react to the same event.

2. Middleware in web servers to process HTTP requests (authentication, logging, response formatting).

3. Input validation and processing in form submissions.

5. Implementation Steps

1. Define the Handler interface that declares a method for handling requests and (optionally) setting the next handler in the chain.

2. Concrete Handlers implement the Handler interface, and they either process the request or pass it to the next handler in the chain.

3. Clients create a chain of handlers and initiate the request to the first handler in the chain.

6. Implementation in Go

// Handler interface
type Handler interface {
	SetNext(Handler)
	HandleRequest(string) bool
}
// ConcreteHandlerA
type ConcreteHandlerA struct {
	next Handler
}
func (h *ConcreteHandlerA) SetNext(handler Handler) {
	h.next = handler
}
func (h *ConcreteHandlerA) HandleRequest(request string) bool {
	if request == "A" {
		fmt.Println("HandlerA handling request A")
		return true
	} else if h.next != nil {
		return h.next.HandleRequest(request)
	}
	return false
}
// ConcreteHandlerB
type ConcreteHandlerB struct {
	next Handler
}
func (h *ConcreteHandlerB) SetNext(handler Handler) {
	h.next = handler
}
func (h *ConcreteHandlerB) HandleRequest(request string) bool {
	if request == "B" {
		fmt.Println("HandlerB handling request B")
		return true
	} else if h.next != nil {
		return h.next.HandleRequest(request)
	}
	return false
}
// Client code
func main() {
	handlerA := &ConcreteHandlerA{}
	handlerB := &ConcreteHandlerB{}
	handlerA.SetNext(handlerB)
	requests := []string{"A", "B", "C"}
	for _, request := range requests {
		handled := handlerA.HandleRequest(request)
		if !handled {
			fmt.Printf("Request %s was not handled\n", request)
		}
	}
}

Output:

HandlerA handling request A
HandlerB handling request B
Request C was not handled

Explanation:

1. The Handler interface defines the contract for handling requests and chaining handlers.

2. ConcreteHandlerA and ConcreteHandlerB are implementations of the Handler. They check if they can handle the request. If not, they pass it to the next handler in the chain.

3. In the client code, we set up a chain where handlerA is followed by handlerB. The requests are sent to handlerA, which either handles them or forwards them to handlerB.

7. When to use?

Use the Chain of Responsibility Pattern when:

1. You want to decouple a sender from its receivers.

2. Multiple objects, determined at runtime, should process a request.

3. You want to provide a flexible and efficient way to organize request handling logic.


Comments