亦称:职责链模式、命令链、CoR、Chain of Command、Chain of Responsibility

责任链模式将请求沿着处理者链依次进行发送。收到请求后,每个处理者均可对请求进行处理,或将其传递给链上的下个处理者。

责任链模式提供了很好的扩展性,并且能够去掉if-else。

背景

假如需要开发一个在线订购系统。希望对系统访问进行限制,只允许认证用户创建订单。此外,拥有管理权限的用户也拥有所有订单的完全访问权限。

只要接收到包含用户凭据的请求,应用程序就可尝试对进入系统的用户进行认证。但如果由于用户凭据不正确而导致认证失败,就没有必要进行后续检查了。

在开发过程中,又添加了几个步骤:

  • 添加一个验证步骤来校验请求中的数据。
  • 添加一个检查步骤来过滤来自同一IP地址的重复错误请求。
  • 新增一个检查步骤,确保只在没有满足条件的缓存结果时才能通过请求并发送给系统。

局面:

  • 检查代码本来就已经混乱不堪,而每次新增功能都会使其更加臃肿。
  • 修改某个检查步骤时会影响其他的检查步骤。
  • 最糟糕的是,当希望复用这些检查步骤来保护其他系统组件时,只能复制部分代码,因为这些组件只需部分而非全部的检查步骤。

解决方案

与许多其他行为设计模式一样,责任链会将特定行为转换为被称作处理者的独立对象。

  • 在上述示例中,每个检查步骤都可被抽取为仅有单个方法的类,提供检查操作。
  • 请求及其数据则会被作为参数传递给该方法。

责任链模式将这些处理者连成一条链,链上的每个处理者都有一个成员变量来保存对于下一处理者的引用。

  • 除了处理请求外,处理者还负责沿着链传递请求。
  • 请求会在链上移动,直至所有处理者都有机会对请求进行处理。

最重要的是:处理者可以决定要不要沿着链继续传递请求,这样可以高效地取消所有后续处理步骤。

还有一种稍微不同的更经典的方式,处理者接收到请求后自行决定是否能够对其进行处理。

  • 如果自己能够处理,处理者就不再继续传递请求。
  • 在这种情况下,每个请求要么最多有一个处理者对其进行处理,要么没有任何处理者对其进行处理

连成链的方式比较多样,可以用UML中展示的那样,一个处理对象使用SetNext()引用下一个处理对象。 也可以使用array或者list存储所有处理对象,使用循环方式遍历。

  • 对于第二种方式,感觉有些像观察者模式。
  • 两者具体实现、目的都差不多。主要区别在于:
    • 观察者模式中的处理对象功能可能完全无关,观察者模式主要负责将信息传递给处理对象即可
    • 职责链模式的处理对象功能一般相似,另外职责链模式也关注请求是否正确被处理

责任链模式

职责链模式的核心在于将处理对象整理成链路。

适用场景

  • 程序需要使用不同方式处理请求
    • 将多个处理者连接成一条链。接收到请求后,“询问” 每个处理者是否能对其进行处理。这样所有处理者都有机会来处理请求。
  • 当必须按顺序执行多个处理者时,可以使用该模式。
    • 无论你以何种顺序将处理者连接成一条链,所有请求都会严格按照顺序通过链上的处理者。
  • 如果所需处理者及其顺序必须在运行时进行改变,可以使用责任链模式。
    • 如果在处理者类中有对引用成员变量的设定方法,能动态地插入和移除处理者,或者改变其顺序。

实现步骤

  1. 声明处理者接口并提供请求处理方法的签名。
    • 确定客户端如何将请求数据传递给方法。 最灵活的方式是将请求转换为对象, 然后将其以参数的形式传递给处理函数。
  2. 为了消除具体处理者中的重复代码,可以根据处理者接口创建抽象处理者基类。
    • 该类需要有一个成员变量来存储指向链上下一个处理者的引用。如果需要在运行时对链进行改变,需要定义一个设定方法来修改引用成员变量的值。
    • 还可以提供处理方法的默认行为。如果还有剩余对象,默认行为直接将请求传递给下个对象。具体处理者可以通过调用父对象的方法来使用这一行为。
  3. 依次创建具体处理者子类并实现其处理方法。 每个处理者在接收到请求后都必须做出两个决定:
    • 是否自行处理这个请求
    • 是否将该请求沿着链进行传递
  4. 客户端可以自行组装链,或者从其他对象处获得预先组装好的链。
    • 在后一种情况下,需要实现工厂类来根据配置或环境设置来创建链
  5. 客户端可以触发链中的任一处理者,不仅仅是第一个。请求将通过链进行传递,直至某个处理者拒绝继续传递,或者请求到达链尾。
  6. 由于链的动态性,客户端需要处理以下情况:
    • 部分请求可能无法到达链尾
    • 其他请求可能直到链尾都未被处理

优缺点

优点:

  • 可以控制请求处理的顺序。
  • 单一职责原则。解耦了发起操作和执行操作的类。
  • 开闭原则。 可以在不更改现有代码的情况下在程序中新增处理者。

缺点:

  • 部分请求最终可能都未被处理。

与其它模式的关系

  • 责任链模式、命令模式、中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式:
    • 责任链模式按照顺序将请求动态传递给一系列的潜在接收者。
    • 命令模式在发送者和请求者之间建立单向连接。
    • 中介者模式清除了发送者和请求者之间的直接连接,强制它们通过一个中介对象进行间接沟通。
    • 观察者模式允许接收者动态地订阅或取消接收请求。
  • 责任链可以和组合模式结合使用
    • 叶组件接收到请求后,将请求沿包含全体父组件的链一直传递至对象树的底部。
  • 责任链上的处理器可使用命令模式实现
    • 可以对由请求代表的同一个上下文对象执行许多不同的操作。
    • 或者,请求自身就是一个命令对象。可以对一系列不同对象组成的链执行相同的操作。
  • 责任链装饰模式的类结构非常相似。 两者都依赖递归组合将需要执行的操作传递给对象。两者也有几点不同
    • 责任链上的处理器可以相互独立地执行,还可以随时停止传递请求
    • 各种装饰可以在遵循基本接口的情况下扩展对象的行为
    • 装饰无法中断请求的传递

示例

就医流程

医院中会有多个部门, 如:

  1. 前台
  2. 医生
  3. 药房
  4. 收银

病人来访时都会先去前台,然后是看医生、取药,最后结账(国内一般是先缴费后取药…)。 也就是说,病人需要通过一条部门链,每个部门都在完成其职能后将病人进一步沿着链条输送。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package main

// department 部门
type department interface {
	execute(*patient)
	setNext(department)
}

// reception 前台
type reception struct {
	next department
}

func (r *reception) execute(p *patient) {
	if p.registrationDone {
		fmt.Println("Patient registration already done")
		r.next.execute(p)
		return
	}
	fmt.Println("Reception registering patient")
	p.registrationDone = true
	r.next.execute(p)
}

func (r *reception) setNext(next department) {
	r.next = next
}

// doctor 医生
type doctor struct {
	next department
}

func (d *doctor) execute(p *patient) {
	if p.doctorCheckUpDone {
		fmt.Println("Doctor checkup already done")
		d.next.execute(p)
		return
	}
	fmt.Println("Doctor checking patient")
	p.doctorCheckUpDone = true
	d.next.execute(p)
}

func (d *doctor) setNext(next department) {
	d.next = next
}


// medical 药房
type medical struct {
	next department
}

func (m *medical) execute(p *patient) {
	if p.medicineDone {
		fmt.Println("Medicine already given to patient")
		m.next.execute(p)
		return
	}
	fmt.Println("Medical giving medicine to patient")
	p.medicineDone = true
	m.next.execute(p)
}

func (m *medical) setNext(next department) {
	m.next = next
}

// cashier 收银
type cashier struct {
	next department
}

func (c *cashier) execute(p *patient) {
	if p.paymentDone {
		fmt.Println("Payment Done")
	}
	fmt.Println("Cashier getting money from patient patient")
}

func (c *cashier) setNext(next department) {
	c.next = next
}
1
2
3
4
5
6
7
8
9
package main

type patient struct {
	name              string
	registrationDone  bool
	doctorCheckUpDone bool
	medicineDone      bool
	paymentDone       bool
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

func main() {
	reception := &reception{}
	doctor := &doctor{}
	medical := &medical{}
	cashier := &cashier{}

	reception.setNext(doctor) // 前台 -> 医生
	doctor.setNext(medical)   // 医生 -> 药房
	medical.setNext(cashier)  // 药房 -> 收银

	patient := &patient{name: "abc"}
	reception.execute(patient) // 从前台开始执行
}

Gin中间件

Web框架中,请求到达后需要先经过若干处理:敏感词脱敏、过滤器、拦截器等,可以使用责任链模式管理。 研发人员能够快速扩展出自己的过滤器和拦截器。

Gin框架里面有全局中间件的概念,而全局中间件使用的便是职责链模式。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package main

import (
	"errors"
)

type HandlerFunc func() error

type HandlersChain []HandlerFunc

type RouterGroup struct {
	Handlers HandlersChain
	Status   int8
	index    int8
}

func (g *RouterGroup) Use(middleware ...HandlerFunc) {
	g.Handlers = append(g.Handlers, middleware...)
}

func (g *RouterGroup) Next() {
	for ; g.index < int8(len(g.Handlers)); g.index++ {
		if err := g.Handlers[g.index](); err != nil {
			g.Status = 1
			break
		}
	}
}

func middleware1() error {
	// do something
	return nil
}

func middleware2() error {
	// do something
	return errors.New("err")
}

func main() {
	r := &RouterGroup{}
	r.Use(middleware1, middleware2)
	r.Next()

	if r.Status == 1 {
		print("中间件检查不通过")
		return
	}

	//执行后续流程
}

References

guru