Skip to content

Commit c8ccd4f

Browse files
committed
adding the chain of responsibility pattern 📚
1 parent 8fe9190 commit c8ccd4f

File tree

5 files changed

+223
-0
lines changed

5 files changed

+223
-0
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Design Patterns: Chain of Responsibility ⛓
2+
3+
The Chain of responsibility pattern is a behavioral design pattern that deals with breaking down a complex problem into sequentially occurring steps. At each step, there is a possibility of moving to the next step or exiting from the chain.
4+
5+
To understand this better, let's understand how a basic call center would work. Think of all the stages you'd go through when you ring up the customer care of an electronics store to get some help regarding a new product you bought.
6+
7+
### Customer Care
8+
9+
A generic customer care you follow the following steps:
10+
11+
1. The user would start off by interacting with an automated voice assistant. The user can choose to continue or drop off.
12+
2. Next, the user would be redirected to the call center associate. Based on the query, the associate can choose to resolve it or progress the user to the next level.
13+
3. The final stage; the user is redirected to the manager - who can take care of almost anything.
14+
15+
**There's also a special case, if the customer is a high priority customer, the system skips the conversation with the associate and directly connects the customer to the manager.**
16+
17+
Let's imagine how we would code this. 🤔 A naïve approach would be to have a single function that the user enters and then based on the user's status and interactions, different sub-functions (voice assistant, associate or manager) are called.
18+
19+
However, as our system grows and we want to add more features, this approach would make the function difficult to manage and the sub-functions difficult to reuse. More importantly, this approach **violates the SOLID programming principle of separating concerns**. 🤕
20+
21+
As an alternative, we could design our sub-functions in such a way that they would immediately call the next step in the chain as soon as their execution is over. In that way, the functionality for every step is maintained in a separate function but also it's easier to swap pieces out from the chain/insert more steps in the chain 🔁
22+
23+
Let's check out the implementation for this in go:
24+
25+
```go
26+
type step interface {
27+
run(*customer)
28+
setNextStep(step)
29+
}
30+
```
31+
32+
```go
33+
type customer struct {
34+
name string
35+
isHighPriority bool
36+
}
37+
```
38+
39+
```go
40+
type voiceAssistant struct {
41+
next step
42+
}
43+
44+
func (v *voiceAssistant) run(cust *customer) {
45+
fmt.Println("[Voice Assistant] Serving the customer: ", cust.name)
46+
v.next.run(cust)
47+
}
48+
49+
func (v *voiceAssistant) setNextStep(next step) {
50+
v.next = next
51+
}
52+
```
53+
54+
```go
55+
type associate struct {
56+
next step
57+
}
58+
59+
func (a *associate) run(cust *customer) {
60+
if cust.isHighPriority {
61+
fmt.Println("Redirecting customer directly to manager")
62+
a.next.run(cust)
63+
return
64+
}
65+
fmt.Println("[Associate] Serving the customer: ", cust.name)
66+
a.next.run(cust)
67+
}
68+
69+
func (a *associate) setNextStep(next step) {
70+
a.next = next
71+
}
72+
```
73+
74+
```go
75+
type manager struct {
76+
next step
77+
}
78+
79+
func (a *manager) run(cust *customer) {
80+
fmt.Println("[Manager] Serving the customer: ", cust.name)
81+
}
82+
83+
func (a *manager) setNextStep(next step) {
84+
a.next = next
85+
}
86+
```
87+
88+
At this point, each link of our chain is now created. We need to arrange them properly so that they follow the desired workflow: `Voice Assistant -> Associate(optional) -> Manager`. Let's do this in our main function for two different customers and see the result
89+
90+
```go
91+
func main() {
92+
m := &manager{}
93+
94+
assoc := &associate{};
95+
assoc.setNextStep(m)
96+
97+
va := &voiceAssistant{};
98+
va.setNextStep(assoc);
99+
100+
// Chain formation complete
101+
102+
// Start chain execution for normal customer
103+
normalCust := &customer{
104+
name: "Bob"
105+
}
106+
107+
va.run(normalCust);
108+
109+
fmt.Println("===================")
110+
111+
// Start chain execution for high priority customer
112+
highPriorityCust := &customer{
113+
name: "John",
114+
isHighPriority: true,
115+
}
116+
117+
va.run(highPriorityCust)
118+
}
119+
```
120+
121+
This should give you the following output:
122+
123+
```text
124+
[Voice Assistant] Serving the customer: Bob
125+
[Associate] Serving the customer: Bob
126+
[Manager] Serving the customer: Bob
127+
===================
128+
[Voice Assistant] Serving the customer: John
129+
Redirecting customer directly to manager
130+
[Manager] Serving the customer: John
131+
```
132+
133+
That's the Chain of Responsibility pattern for you! 😁
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package main
2+
3+
type customer struct {
4+
name string
5+
isHighPriority bool
6+
}

9-chain-of-responsibility/main.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package main
2+
3+
import "fmt"
4+
5+
func main() {
6+
m := &manager{}
7+
8+
assoc := &associate{}
9+
assoc.setNextStep(m)
10+
11+
va := &voiceAssistant{}
12+
va.setNextStep(assoc)
13+
14+
// Chain formation complete
15+
16+
// Start chain execution for normal customer
17+
normalCust := &customer{
18+
name: "Bob",
19+
}
20+
21+
va.run(normalCust)
22+
23+
fmt.Println("===================")
24+
25+
// Start chain execution for high priority customer
26+
highPriorityCust := &customer{
27+
name: "John",
28+
isHighPriority: true,
29+
}
30+
31+
va.run(highPriorityCust)
32+
}

9-chain-of-responsibility/step.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package main
2+
3+
type step interface {
4+
run(*customer)
5+
setNextStep(step)
6+
}

9-chain-of-responsibility/steps.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package main
2+
3+
import "fmt"
4+
5+
type voiceAssistant struct {
6+
next step
7+
}
8+
9+
func (v *voiceAssistant) run(cust *customer) {
10+
fmt.Println("[Voice Assistant] Serving the customer: ", cust.name)
11+
v.next.run(cust)
12+
}
13+
14+
func (v *voiceAssistant) setNextStep(next step) {
15+
v.next = next
16+
}
17+
18+
type associate struct {
19+
next step
20+
}
21+
22+
func (a *associate) run(cust *customer) {
23+
if cust.isHighPriority {
24+
fmt.Println("Redirecting customer directly to manager")
25+
a.next.run(cust)
26+
return
27+
}
28+
fmt.Println("[Associate] Serving the customer: ", cust.name)
29+
a.next.run(cust)
30+
}
31+
32+
func (a *associate) setNextStep(next step) {
33+
a.next = next
34+
}
35+
36+
type manager struct {
37+
next step
38+
}
39+
40+
func (a *manager) run(cust *customer) {
41+
fmt.Println("[Manager] Serving the customer: ", cust.name)
42+
}
43+
44+
func (a *manager) setNextStep(next step) {
45+
a.next = next
46+
}

0 commit comments

Comments
 (0)