Table of contents
The Chain of Responsibility pattern is a behavioral design pattern that allows an object to pass a request along a chain of handlers until one of the handlers can handle the request. This pattern is useful when there are multiple objects that can handle a request, but we do not know which object can handle the request until runtime.
The Gang of Four, in their book "Design Patterns: Elements of Reusable Object-Oriented Software", first introduced the Chain of Responsibility pattern.
Implementation in Golang
Let's implement the Chain of Responsibility pattern in Golang. We will use a realistic example where a customer's order is handled by different departments in a company.
type Order struct {
// ...
}
type OrderHandler interface {
SetNext(OrderHandler) OrderHandler
Handle(*Order) error
}
type OrderProcessor struct {
handler OrderHandler
}
func (o *OrderProcessor) SetHandler(handler OrderHandler) {
o.handler = handler
}
func (o *OrderProcessor) Process(order *Order) error {
return o.handler.Handle(order)
}
type WarehouseHandler struct {
next OrderHandler
}
func (w *WarehouseHandler) SetNext(next OrderHandler) OrderHandler {
w.next = next
return next
}
func (w *WarehouseHandler) Handle(order *Order) error {
if order.WarehouseFilled {
if w.next != nil {
return w.next.Handle(order)
}
return nil
}
return errors.New("warehouse not filled")
}
type ShippingHandler struct {
next OrderHandler
}
func (s *ShippingHandler) SetNext(next OrderHandler) OrderHandler {
s.next = next
return next
}
func (s *ShippingHandler) Handle(order *Order) error {
if order.ShippingFilled {
if s.next != nil {
return s.next.Handle(order)
}
return nil
}
return errors.New("shipping not filled")
}
type BillingHandler struct {
next OrderHandler
}
func (b *BillingHandler) SetNext(next OrderHandler) OrderHandler {
b.next = next
return next
}
func (b *BillingHandler) Handle(order *Order) error {
if order.BillingFilled {
if b.next != nil {
return b.next.Handle(order)
}
return nil
}
return errors.New("billing not filled")
}
In the above code, we have defined the interface OrderHandler
and a struct OrderProcessor
. The OrderProcessor
has a reference to an implementation of the OrderHandler
interface.
We have also defined different handlers for different departments - WarehouseHandler
, ShippingHandler
, and BillingHandler
. Each handler has a reference to the next handler in the chain.
The Handle
function in each handler checks if the specific department has completed the order. If it has, it passes the request to the next handler in the chain. If not, it returns an error.
Usage
Let's see how we can use the Chain of Responsibility pattern in our example.
func main() {
order := &Order{ /* ... */ }
orderProcessor := &OrderProcessor{}
warehouseHandler := &WarehouseHandler{}
shippingHandler := &ShippingHandler{}
billingHandler := &BillingHandler{}
warehouseHandler.SetNext(shippingHandler)
shippingHandler.SetNext(billingHandler)
orderProcessor.SetHandler(warehouseHandler)
if err := orderProcessor.Process(order); err != nil {
fmt.Println(err)
return
}
fmt.Println("Order processed successfully")
}
In the above code, we have created an order and an instance of the OrderProcessor
. We have also created instances of the different handlers and set up the chain of responsibility.
Finally, we pass the order to the OrderProcessor
and check if there were any errors.
Conclusion
The Chain of Responsibility pattern is a powerful pattern that allows us to handle requests in a flexible and extensible way. In Golang, we can implement this pattern using interfaces and structs, as shown in the example above.
References
- “Design Patterns: Elements of Reusable Object-Oriented Software” by Erich Gamma, John Vlissides, Ralph Johnson, and Richard Helm