Introduction
The Chain of Responsibility (CoR) pattern is a behavioral design pattern that allows multiple objects to process a request sequentially until one of them handles it. This pattern promotes loose coupling and enhances flexibility in request processing by decoupling senders and receivers.
When to Use the Chain of Responsibility Pattern
When multiple handlers could process a request, and the handler is determined dynamically.
When you want to decouple the sender from the receiver.
When requests should be processed sequentially until a suitable handler is found.
When implementing logging, event processing, middleware chains, or authorization checks.
Implementing Chain of Responsibility in Golang
Let’s take an example where we build a logging system with different log levels (INFO, WARNING, ERROR). Each log level handler will decide whether to handle the request or pass it to the next handler in the chain.
Step 1: Define the Handler Interface
The handler interface declares a method to process requests and a method to set the next handler in the chain.
package main
import "fmt"
// Handler interface defines the methods for processing requests.
type Handler interface {
SetNext(handler Handler)
HandleRequest(level int, message string)
}
Step 2: Implement Concrete Handlers
Each concrete handler will check if it can process the request based on the log level and then either handle it or pass it to the next handler.
// BaseHandler provides common functionality for all handlers.
type BaseHandler struct {
next Handler
}
// SetNext sets the next handler in the chain.
func (b *BaseHandler) SetNext(handler Handler) {
b.next = handler
}
// InfoLogger handles INFO level logs.
type InfoLogger struct {
BaseHandler
}
func (i *InfoLogger) HandleRequest(level int, message string) {
if level == 1 {
fmt.Println("INFO: ", message)
} else if i.next != nil {
i.next.HandleRequest(level, message)
}
}
// WarningLogger handles WARNING level logs.
type WarningLogger struct {
BaseHandler
}
func (w *WarningLogger) HandleRequest(level int, message string) {
if level == 2 {
fmt.Println("WARNING: ", message)
} else if w.next != nil {
w.next.HandleRequest(level, message)
}
}
// ErrorLogger handles ERROR level logs.
type ErrorLogger struct {
BaseHandler
}
func (e *ErrorLogger) HandleRequest(level int, message string) {
if level == 3 {
fmt.Println("ERROR: ", message)
} else if e.next != nil {
e.next.HandleRequest(level, message)
}
}
Step 3: Assemble the Chain and Use It
func main() {
// Creating handlers
infoLogger := &InfoLogger{}
warningLogger := &WarningLogger{}
errorLogger := &ErrorLogger{}
// Setting up the chain: INFO -> WARNING -> ERROR
infoLogger.SetNext(warningLogger)
warningLogger.SetNext(errorLogger)
// Using the chain
fmt.Println("Logging Example:")
infoLogger.HandleRequest(1, "This is an informational message.")
infoLogger.HandleRequest(2, "This is a warning message.")
infoLogger.HandleRequest(3, "This is an error message.")
}
Explanation
Handlers Implement a Chain: Each handler implements the
HandleRequest
method and passes unhandled requests to the next handler.Encapsulation of Processing Logic: Each handler decides whether to process the request or delegate it.
Flexibility: Adding new handlers is easy without modifying existing ones.
Reusability: Handlers can be reused in different chains.
Real-World Applications
Middleware in Web Frameworks: Golang’s
http.Handler
follows a similar pattern for processing requests (authentication, logging, response modification, etc.).Authorization Mechanisms: A request passes through a chain of checks (e.g., role-based access control).
Event Processing Systems: Events are passed through different processors for transformation or validation.
Conclusion
The Chain of Responsibility pattern in Golang is a powerful way to structure request handling in a flexible and maintainable way. By designing handlers that can either process or delegate requests, we create highly modular and reusable systems. Understanding and applying this pattern will enhance your ability to design robust and scalable applications in Golang.
Would you like to see an advanced example, such as handling API requests with middleware chaining? Let me know!