Welcome back! Building on our exploration of various behavioral patterns, we will delve into the Chain of Responsibility Pattern in this lesson. This pattern allows a request to pass through a chain of handlers, giving multiple objects a chance to process the request. It's an excellent way to achieve loose coupling in your code by enabling the request to be processed by different handlers independently.
In this lesson, you will learn how to implement the Chain of Responsibility Pattern in Go
. This pattern allows you to handle requests flexibly and dynamically. Specifically, you will learn to:
Handler
Interface: Define a common interface for all your handlers.Let's quickly go through an example to understand these concepts:
We start by defining a Request
struct and a Handler
interface. The Request
struct contains the message and the level of the request, while the Handler
interface defines the common methods that all handlers must implement:
Go1type Request struct { 2 message string 3 level int 4} 5 6type Handler interface { 7 Handle(request Request) bool 8 SetNext(handler Handler) 9}
Next, we create a BaseHandler
struct that implements the Handler
interface. Notice, the BaseHandler
contains a reference to the next handler in the chain by using the next
field. The Handle
method in the BaseHandler
passes the request to the next handler if it exists:
Go1// Base Handler 2type BaseHandler struct { 3 next Handler 4} 5 6func (h *BaseHandler) SetNext(handler Handler) { 7 h.next = handler 8} 9 10func (h *BaseHandler) Handle(request Request) bool { 11 if h.next != nil { 12 return h.next.Handle(request) 13 } 14 return false 15}
Now, we create specific handlers that extend the BaseHandler
and implement the Handle
method. In our example, we have FrontDeskHandler
, ManagerHandler
, and DirectorHandler
. Each handler checks if it can handle the request based on the request level. If it can't, it passes the request to the next handler in the chain:
Go1// Front Desk Handler 2type FrontDeskHandler struct { 3 BaseHandler 4} 5 6func (h *FrontDeskHandler) Handle(request Request) bool { 7 if request.level == 1 { 8 fmt.Println("Request handled by Front Desk") 9 return true 10 } 11 return h.BaseHandler.Handle(request) 12} 13 14// Manager Handler 15type ManagerHandler struct { 16 BaseHandler 17} 18 19func (h *ManagerHandler) Handle(request Request) bool { 20 if request.level == 2 { 21 fmt.Println("Request handled by Manager") 22 return true 23 } 24 return h.BaseHandler.Handle(request) 25} 26 27// Director Handler 28type DirectorHandler struct { 29 BaseHandler 30} 31 32func (h *DirectorHandler) Handle(request Request) bool { 33 if request.level == 3 { 34 fmt.Println("Request handled by Director") 35 return true 36 } 37 return h.BaseHandler.Handle(request) 38}
Finally, we set up the chain of handlers and initiate the request by calling the Handle
method of the first handler in the chain:
Go1func main() { 2 frontDesk := &FrontDeskHandler{} 3 manager := &ManagerHandler{} 4 director := &DirectorHandler{} 5 6 frontDesk.SetNext(manager) 7 manager.SetNext(director) 8 9 request := Request{message: "General inquiry", level: 1} 10 frontDesk.Handle(request) // Output: Request handled by Front Desk 11 12 request = Request{message: "Complaint", level: 2} 13 frontDesk.Handle(request) // Output: Request handled by Manager 14 15 request = Request{message: "High-level issue", level: 3} 16 frontDesk.Handle(request) // Output: Request handled by Director 17}
Notice how the request passes through the chain of handlers until it's processed. This is the essence of the Chain of Responsibility Pattern! As you can see, this pattern allows you to handle requests flexibly and dynamically by passing them through a chain of handlers. Therefore, the first request with level 1 is handled by the FrontDeskHandler
, the second request with level 2 is handled by the ManagerHandler
as front desk can't handle it, and the third request with level 3 is handled by the DirectorHandler
as the manager can't handle it.
Let's understand the key components of the Chain of Responsibility Pattern:
Handle
method processes the request, and the SetNext
method sets the next handler in the chain.Handler
interface. It contains the logic to pass the requestBaseHandler
and implement the Handle
method. They decide whether to handle the request or pass it to the next handler in the chain. In our example, we have FrontDeskHandler
, ManagerHandler
, and DirectorHandler
.Let's now understand the flow of the request in our example:
Request
object is passed to the FrontDeskHandler
. If the request level matches the handler's level, it handles the request; otherwise, it passes the request to the next handler in the chain.ManagerHandler
receives the request and checks if it can handle it. If not, it passes the request to the DirectorHandler
.DirectorHandler
processes the request if it matches its level.Handle
method of the first handler in the chain.The Chain of Responsibility Pattern is useful in various scenarios, such as:
Pros
Cons
Understanding and implementing the Chain of Responsibility Pattern is vital for several reasons:
Picture a customer service department where requests can be handled at different levels. A simple question might be answered by the front desk, a complaint by a manager, and a serious issue by a director. By using the Chain of Responsibility Pattern, you can easily model this hierarchy in your code.
Excited to see how flexible and powerful this pattern is? Let's dive into the practice section and put this knowledge into action!