Skip to main content

Command Palette

Search for a command to run...

Golang - Chain of Responsibility Pattern

Updated
3 min read
Golang - Chain of Responsibility Pattern
M

Senior Freelancer & Technical Lead

Working as a Golang developer since 2020. Working as a mobile developer since 2013.

Focussed on architecture, testability and clean code. Open minded & product driven. Based in Rhede, available world-wide

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

Golang Patterns

Part 12 of 19

Welcome to the Golang Patterns tutorial series! In this series, we will explore the various software patterns that can be applied in Golang to create flexible, scalable, and maintainable software.

Up next

Golang - Decorator Pattern

The Decorator Pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class. The "Gang of Four" patterns book describes the Decorator Pattern as ...