切换语言为:繁体

go 实现责任链模式

  • 爱糖宝
  • 2024-08-27
  • 2054
  • 0
  • 0

一、问题

  • 业务扩张与缩减过程中,对应地就会有功能的增减,如果每次功能的增减都需要改动非常多的代码,就会导致代码逻辑不够清晰易懂,业务逻辑混乱的问题;

  • 在实际开发中,将整个业务流程拆分不同的子流程,对应地每个代码块节点处理一个子流程,最后将这些代码块串起来执行,即可对应整个业务流程;

  • 当该条业务有子流程增减时,只需要增减对应的代码块节点即可,这就是应用了责任链模式;

二、基本介绍

  • 责任链模式是一种行为设计模式,用于将请求的发送者和接收者解耦,通过多个处理节点对请求的处理,将请求的传递与处理链条化

  • 在实际应用中,责任链模式常用于处理复杂的请求处理流程,使得请求能够沿着处理链依次传递,每个处理节点负责判断能否处理请求并进行处理或转发;

  • 每个处理节点对应一个业务子流程,当业务子流程有增减时只需要对应的增减处理节点;

三、模式结构

  • 责任链模式主要包含接口、子节点实现、数据类型、客户端四部分:

  • 接口:定义处理请求数据的方法,通常包括具体的数据处理方法、责任链转移方法和子节点链接方法;

  • 子节点实现:实现接口定义的方法,负责处理请求数据和节点转移;

  • 数据类型:定义请求与返回的数据结构,各个节点从该结构中读取要处理的数据,以及写回处理后的数据;

  • 客户端:创建并组装责任链,向责任链的起始点提交请求数据;

四、工作原理

  • 请求提交:客户端创建请求数据并组装责任链,将请求数据提交给责任链的第一个处理者(即起始点);

  • 处理判断:每个子节点处理者根据自身的业务逻辑判断是否能够处理该请求;如果可以处理,则处理请求并写回结果,遇到错误时可以提前返回;如果不能处理,则将请求传递给责任链中的下一个处理者;

  • 责任链传递:请求沿着责任链依次传递,直到找到能够处理请求的处理者为止;

  • 请求完成:当某个处理者成功处理请求后,责任链模式可以选择终止传递或继续传递给下一个处理者。

五、实例分析

Handler 接口

  • Handler 是每个逻辑节点的接口,定义了三个方法,分别是:

    • Execute(*Data) error:接收请求数据,负责逻辑节点的执行与转移,即从前往后依次执行责任链上的每个节点,直至出错返回或是执行完毕;

    • SetNext(Handler) Handler:负责将某个节点链接到当前节点的后面,并返回后一个节点,保证能够依次链接器所有的逻辑节点;

    • Do(*Data) error:接收请求数据,真正的逻辑处理的部分,由具体的业务逻辑节点实现,在Execute方法中被调用;

中间对象 Next

  • Next 结构体,是一个没有具体逻辑的抽象节点,它包含了 Handler 接口的元素,用来存储下一个逻辑节点;

  • Next 结构体实现了 Handler 接口中的 Execute 方法和 SetNext 方法,但是没有实现 Do 方法,所以其没有实现 Handler 接口;

  • SetNext 方法用于将下一个逻辑节点存储到 nextHandler 中,并且返回 nextHandler 的地址;

  • Execute 方法会调用 nextHandler 的 Do 方法:如果 nextHandler 为空,则说明达到末尾,直接返回;否则调用 nextHandler 的 Do 方法,执行具体子节点的逻辑处理。如果子节点逻辑执行中失败了,则提前返回,不再执行后续节点;执行成功了,则进行逻辑节点的转移,继续执行下一个节点的逻辑处理;

  • 此时 Next 结构体可以理解为一个中间对象,其实现了 ExecuteSetNext 方法,避免了在 A、B、C 中重复实现这两个方法,子节点只需要负责具体的逻辑处理,而不需要关注责任链的拼接与转移;

  • Next 结构体没有实现 Do 方法,不能在 Execute 方法中直接调用自己的 Do 方法,只能调用 nextHandler 的 Do 方法,以下是一个错误的示例;

func (n *Next) Execute(data *Data) (err error) {
	if n == nil {
		return
	}
	if err = n.Do(data); err != nil {
		return
	}

	return n.nextHandler.Execute(data)
}

子节点

  • A、B、C 结构体就是处理具体逻辑的子节点,都是包含了 Next 匿名组合体的结构,其实现了 Do 方法,而 Next 实现了 Execute 和 SetNext 方法,那么 A、B、C 就都实现了 Handler 接口,可以被当作一个子节点,被链接到责任链上;

  • Do 方法从 Data 中接收传入的数据,同时也可以将结果写回 Data,处理中出现错误时,也可以提前返回;

  • StartHandler 也是一个子节点,不过不处理任何逻辑,只是作为第一个 Handler 向下转发请求;

// 责任链模式
package main

import "fmt"

type Handler interface {
	Execute(*Data) error      // 负责逻辑节点的执行与转移
	SetNext(Handler) Handler  // 负责串起各个逻辑节点
	Do(*Data) error           // 真正的逻辑处理的部分
}

type Next struct {
	nextHandler Handler
}

func (n *Next) SetNext(handler Handler) Handler {
	n.nextHandler = handler
	return handler
}

func (n *Next) Execute(data *Data) (err error) {
	if n.nextHandler == nil {
		return
	}
	if err = n.nextHandler.Do(data); err != nil {
		return
	}

	return n.nextHandler.Execute(data)
}

type A struct {
	Next
}

func (m *A) Do(d *Data) (err error) {
	if d.IsADone {
		fmt.Println("A has been done")
		return
	}
	fmt.Println("Deal A")
	d.IsADone = true
	return
}

type B struct {
	Next
}

func (m *B) Do(d *Data) (err error) {
	if d.IsBDone {
		fmt.Println("B has been done")
		return
	}
	fmt.Println("Deal B")
	d.IsBDone = true
	return
}

type C struct {
	Next
}

func (m *C) Do(d *Data) (err error) {
	if d.IsCDone {
		fmt.Println("C has been done")
		return
	}
	fmt.Println("Deal C")
	d.IsCDone = true
	return
}

// StartHandler 不做操作,作为第一个 Handler 向下转发请求
type StartHandler struct {
	Next
}

// Do 空 Handler 的 Do
func (h *StartHandler) Do(d *Data) (err error) {
	// 空 Handler 这里什么也不做,只是载体 do nothing...
	fmt.Println("StartHandler do nothing")
	return
}

type Data struct {
	Name    string
	IsADone bool
	IsBDone bool
	IsCDone bool
}

func main() {
	s := StartHandler{}
	s.SetNext(&A{}).SetNext(&B{}).SetNext(&C{}).SetNext(&B{})

  // 执行业务流程中的各个责任点
	data := &Data{Name: "abc"}
	if err := s.Execute(data); err != nil {
		fmt.Println("Error:" + err.Error())
		return
	}
	fmt.Println("Success")
}

六、结论

  • 责任链模式通过将请求的发送者和接收者解耦,提供了一种灵活且可扩展的处理方式;它能够动态地组织和分配责任,使得系统更易于维护和扩展;

  • 责任链模式的核心思想是「链式处理」,在处理复杂业务流程时特别有用,它不仅提高了系统的灵活性和可扩展性,同时也使得代码更加清晰和易于理解;

  • 在实际应用中,责任链模式常用于处理请求的优先级排序、权限校验、日志记录等场景,为复杂系统的请求处理提供了一种有效的解决方案;

0条评论

您的电子邮件等信息不会被公开,以下所有项均必填

OK! You can skip this field.