大家好,我是晴天。我们又见面了,本周我们继续学习设计模式,本周将同大家一起学习责任链模式。
场景引入
我们回想一下自己曾经经历过的考学场景,我们是如何一步一步考上大学的(为了简化过程只提取核心环节)。第一步,我们需要报名考试,第二步,我们需要去参加考试,第三步,我们才能够通过考试,顺利进入大学。这三个步骤必须要串行化执行,只有前一步通过了,我们才能进行后续的步骤,如果前一步没有通过,则提前终止整过流程,无需进行后续步骤了。
什么是责任链模式
责任链设计模式是一种行为型设计模式,它要求完成一件事情必须按照指定顺序进行,前一个环节完成了才能进行下一个环节,否则就提前终止。每个环节可以理解成一个链表的节点,当这个节点接收到上一个节点传送过来的信息时,开始进行自身的逻辑处理,处理完成后,传递给下一个节点,调用下一个节点的处理方法,直到链表遍历完成为止。
为什么需要责任链模式
责任链设计模式是一种行为型设计模式,它主要用于将请求沿着处理链传递,直到有一个处理者能够处理该请求为止。责任链模式有助于降低发送者和接收者之间的耦合度,使系统更加灵活和可扩展。
代码实战
实现顺序执行逻辑有两种处理方法,一种遍历方式,是将处理方法放到数组中,遍历处理方法即可,第二种就是使用责任链模式来处理。我们依次来看一下这两种实现方式。
遍历方式
package mainimport ("context""errors""fmt"
)type Student1 struct {Name stringSignUp bool // 是否考试报名TakeTest bool // 是否参加考试PassExam bool // 是否通过考试
}type handler1 func(ctx context.Context, stu *Student1) error// 报名考试
func SignUp1(ctx context.Context, stu *Student1) error {if stu.SignUp {fmt.Println(stu.Name + "已经报名了考试")return errors.New(stu.Name + "已经报名了考试了")}stu.SignUp = truefmt.Println(stu.Name + "正在进行考试报名")return nil
}// 参加考试
func TakeTest1(ctx context.Context, stu *Student1) error {if stu.TakeTest {fmt.Println(stu.Name + "已经参加了考试")return errors.New(stu.Name + "已经参加过考试了")}stu.TakeTest = truefmt.Println(stu.Name + "正在参加考试")return nil
}// 通过考试
func PassExam1(ctx context.Context, stu *Student1) error {if stu.PassExam {fmt.Println(stu.Name + "已经通过了考试")return errors.New(stu.Name + "已经通过了考试")}stu.PassExam = truefmt.Println(stu.Name + "通过了考试")return nil
}func main() {var handlers = []handler1{SignUp1, TakeTest1, PassExam1}var student = &Student1{Name: "张三",}for _, handler := range handlers {if err := handler(context.Background(), student); err != nil {fmt.Println(err)}}
}// 输出:
张三正在进行考试报名
张三正在参加考试
张三通过了考试
代码解释
上述代码定义了三个方法,分别是报名考试(SignUp1)、参加考试(TakeTest)和通过考试(PassExam),这三种方法按照顺序,组成处理方法数组,依次对Student1进行处理,如果某个方法已经判断状态为 true 了,则说明该环节已经参与过了,不需要再进行后续环节的处理了。
责任链模式
package mainimport "fmt"// 责任链模式
/*定义学生考学场景:step1:报名考试step2:参加考试step3:通过考试
*/// 学生
type Student struct {Name stringSignUp bool // 报名考试TakeTest bool // 参加考试PassExam bool // 通过考试
}type Section interface {Do(s *Student) // 参与改环节setNext(section Section) // 设置下一个环节
}// 学生报名考试
type SignUp struct {next Section
}func (s *SignUp) Do(stu *Student) {if stu.SignUp {fmt.Println(stu.Name + "报名考试成功")s.next.Do(stu)return}stu.SignUp = truefmt.Println(stu.Name + "考生正在报名...")s.next.Do(stu)return
}func (s *SignUp) setNext(section Section) {s.next = section
}// 学生参加考试
type TakeTest struct {next Section
}func (p *TakeTest) Do(stu *Student) {if stu.TakeTest {fmt.Println(stu.Name + "参加考试")p.next.Do(stu)return}stu.TakeTest = truefmt.Println(stu.Name + "考生参加考试...")p.next.Do(stu)return
}func (p *TakeTest) setNext(section Section) {p.next = section
}// 学生通过考试
// 责任链最后一个节点,不需要请求后续节点的 Do 方法了
type PassExam struct {next Section
}func (p *PassExam) Do(stu *Student) {if stu.PassExam {fmt.Println(stu.Name + "通过考试成功")return}stu.PassExam = truefmt.Println(stu.Name + "通过考试...")return
}func (p *PassExam) setNext(section Section) {p.next = section
}func main() {var student = &Student{Name: "张三",SignUp: false,TakeTest: false,PassExam: false,}// 最后一个节点var pe = &PassExam{}// 倒数第二个节点var pt = &TakeTest{}// 倒数第三个节点var su = &SignUp{}// 把责任链的节点连接起来pt.setNext(pe)su.setNext(pt)// 调用责任链的首个节点的执行方法su.Do(student)
}
代码解释
上述代码实现了一个具有三个节点的责任链,三个节点分别是SignUp(报名考试)、TakeTest(参加考试)、PassExam(通过考试),每个责任链的节点都是一个 Section 类型,每个节点都实现了两个方法,一个是执行自身逻辑的 Do 方法,另一个就是连接下一个节点的 setNext 方法。这里注意,最后一个节点 PassExam 不需要调用下一个节点了(已经是最后一个节点了)。
两种处理方式的对比
遍历方式优点:
- 遍历方式结构简单,能够方便地将处理方法按照指定顺序,对请求进行处理。
遍历方式缺点:
- 每个处理方法之间没有必然的联系,无法通过上一个处理方法找到写一个处理方法,关联性不强。
责任链模式优点:
- 节点之间相互串联,关联性比较强,当前节点可以根据自身节点的处理结果来决定是否将请求继续向下传递。
- 执行一整条责任链的逻辑处理,只需要调用头节点处理方法即可,就像是只调用了一个节点的处理方法一样。
总结
本文,我们介绍了什么是责任链模式,为什么需要责任链模式,遍历方式和责任链模式的使用方法,并且对比了遍历方式和责任链模式的优缺点。希望各位读者小伙伴能有些许收获。
写在最后
感谢大家的阅读,晴天将继续努力,分享更多有趣且实用的主题,如有错误和纰漏,欢迎留言给予指正。 更多文章敬请关注作者个人公众号 晴天码字。 我们下期不见不散,to be continued…