行为型设计模式—职责链模式

news/2024/5/19 12:15:34 标签: 责任链模式, 设计模式, golang

职责链模式:从名字可以拆分为 职责 和 链。即能为请求创建一条由多个处理器组成的链路,每个处理器各自负责自己的职责,相互之间没有耦合,完成自己任务后请求对象即传递到链路的下一个处理器进行处理。

如果在写好的执行函数里加上部分步骤,导致需要增加若干个if-else,因为整个流程耦合在一起,修改了以后我们就得把整个流程全测一遍。。 而职责链就是把步骤解耦为执行链条,从而消除牵一发而动全身的后果。(链表插入优点是遍历到前节点插入即可,不需要像顺序表一样移动之后所有元素)

在一些核心的业务中,应用职责链模式能够让我们无痛地扩展业务流程的步骤

实现责任链模式的对象最起码需要包含如下特性:

  • 成员属性

    • nextHandler: 下一个等待被调用的对象实例
  • 成员方法

    • SetNext: 把下一个对象的实例绑定到当前对象的nextHandler属性上;
    • Do: 当前对象业务逻辑入口,他是每个处理对象实现自己逻辑的地方;
    • Execute: 负责职责链上请求的处理和传递;它会调用当前对象的DonextHandler不为空则调用nextHandler.Do

SetNextExecute 这两个行为是每个 ConcreteHandler 都一样的,所以这两个可以交给抽象处理类型来实现,每个具体处理对象再继承抽象类型,即可减少重复操作。

以病人去医院看病这个处理流程为例提供一个具体示例。看病的具体流程如下:

挂号—>诊室看病—>收费处缴费—>药房拿药

利用责任链模式,实现这个流程中的每个步骤,且相互间不耦合,还支持向流程中增加步骤。

先实现职责链模式里的公共部分—即模式的接口和抽象类

type PatientHandler interface {
 Execute(*patient) error
 SetNext(PatientHandler) PatientHandler
 Do(*patient) error
}
// 充当抽象类型,实现公共方法,抽象方法不实现留给实现类自己实现
type Next struct {
 nextHandler PatientHandler
}

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

func (n *Next) Execute(patient *patient) (err error) {
 // 调用不到外部类型的 Do 方法,所以 Next 不能实现 Do 方法
 if n.nextHandler != nil {
  if err = n.nextHandler.Do(patient); err != nil {
   return
  }

  return n.nextHandler.Execute(patient)
 }

 return
}

即使Next实现了Do方法,go语言中语法也不能达到在父类方法中调用子类方法的效果—即在例子里面用Next 类型的Execute方法调用不到外部实现类型的Do方法。

定义职责链要处理的请求,实现处理逻辑和请求传递的DoExecute方法的参数都是流程中要处理的请求。这里是医院接诊的流程,所以定义一个患者类作为流程的请求。

//流程中的请求类--患者
type patient struct {
 Name              string
 RegistrationDone  bool
 DoctorCheckUpDone bool
 MedicineDone      bool
 PaymentDone       bool
}

按照挂号—>诊室看病—>收费处缴费—>药房拿药这个流程定义四个步骤的处理类,来分别实现每个环节的逻辑。

// Reception 挂号处处理器
type Reception struct {
 Next
}

func (r *Reception) Do(p *patient) (err error) {
 if p.RegistrationDone {
  fmt.Println("Patient registration already done")
  return
 }
 fmt.Println("Reception registering patient")
 p.RegistrationDone = true
 return
}

// Clinic 诊室处理器--用于医生给病人看病
type Clinic struct {
 Next
}

func (d *Clinic) Do(p *patient) (err error) {
 if p.DoctorCheckUpDone {
  fmt.Println("Doctor checkup already done")
  return
 }
 fmt.Println("Doctor checking patient")
 p.DoctorCheckUpDone = true
 return
}

// Cashier 收费处处理器
type Cashier struct {
 Next
}

func (c *Cashier) Do(p *patient) (err error) {
 if p.PaymentDone {
  fmt.Println("Payment Done")
  return
 }
 fmt.Println("Cashier getting money from patient patient")
 p.PaymentDone = true
 return
}

// Pharmacy 药房处理器
type Pharmacy struct {
 Next
}


func (m *Pharmacy) Do (p *patient) (err error) {
 if p.MedicineDone {
  fmt.Println("Medicine already given to patient")
  return
 }
 fmt.Println("Pharmacy giving medicine to patient")
 p.MedicineDone = true
 return
}

Recepiton接诊挂号这个步骤提供的逻辑没有调用到,再定义StartHandler 类型,它不提供处理实现只是作为第一个Handler向下转发请求

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

// Do 空Handler的Do
func (h *StartHandler) Do(c *patient) (err error) {
 // 空Handler 这里什么也不做 只是载体 do nothing...
 return
}

这是Go 语法限制,公共方法Exeute并不能像面向对象那样先调用this.Do 再调用this.nextHandler.Do

把处理类串起来执行

func main() {
 patientHealthHandler := StartHandler{}
 //
 patient := &patient{Name: "abc"}
 // 设置病人看病的链路
 patientHealthHandler.SetNext(&Reception{}).// 挂号
  SetNext(&Clinic{}). // 诊室看病
  SetNext(&Cashier{}). // 收费处交钱
  SetNext(&Pharmacy{}) // 药房拿药
 //还可以扩展,比如中间加入化验科化验,图像科拍片等等

 // 执行上面设置好的业务流程
 if err := patientHealthHandler.Execute(patient); err != nil {
  // 异常
  fmt.Println("Fail | Error:" + err.Error())
  return
 }
 // 成功
 fmt.Println("Success")
}

职责链也可以设置中止条件,针对例子就是在Execute方法里加判断,一旦满足中止后就不再继续往链路的下级节点传递请求。

这也是职责链跟装饰器模式的一个区别,装饰器模式无法在增强实体的过程中停止,只能执行完整个装饰链路。

针对那些可能未来经常会变的核心业务流程,可以在设计初期就考虑使用职责链来实现,减轻未来流程不停迭代时不好扩展的痛点。

职责链模式与模板模式的区别在于,模板模式的业务流程通常是规定不变的,而职责链模式业务是可拓展的


http://www.niftyadmin.cn/n/5330691.html

相关文章

后端开发笔记20240106

文章目录 写在前面时间相关的减法操作两个date日期相减获得时间差lacaltime和date做差 sql语句,select子句当作where查询的条件 写在前面 出来创业也一年多了,感觉这一年,后端的开发荒废了很多,都快不如专注做后端开发两三年的学…

day3:基于UDP模型的简单文件下载

思维导图 tftp文件下载客户端实现 #include <head.h> #define SER_PORT 69 #define SER_IP "192.168.125.223" int link_file() {int sfdsocket(AF_INET,SOCK_DGRAM,0);if(sfd-1){perror("socket error");return -1;}return sfd; } int filedownloa…

浅析智慧燃气巡检巡线系统

关键词&#xff1a;智慧燃气、燃气巡检、智慧燃气巡检、巡检机器人、燃气场站巡检机器人、燃气场站巡检机器人系统 在我们的日常生活中&#xff0c;燃气作为一种重要的能源&#xff0c;为我们的烹饪、取暖和工业生产提供了便利。然而&#xff0c;燃气的安全问题也一直是社会关…

计算机网络(超详解!) 第二节 数据链路层(上)

1.数据链路层使用的信道 数据链路层使用的信道主要有以下两种类型&#xff1a; 1.点对点信道&#xff1a;这种信道使用一对一的点对点通信方式。 2.广播信道&#xff1a;这种信道使用一对多的广播通信方式&#xff0c;因此过程比较复杂。广播信道上连接的主机很多&#xff0…

Python 发微信:实现自动化沟通的利器

引言&#xff1a; 在当今信息爆炸的时代&#xff0c;微信已经成为人们日常生活中不可或缺的沟通工具。然而&#xff0c;手动发送微信消息往往耗时耗力&#xff0c;尤其是在需要频繁发送消息的场景下。为了提高工作效率和便利性&#xff0c;我们可以利用 Python 编程语言来实现自…

Rust 错误处理(上)

目录 1、用 panic! 处理不可恢复的错误 对应 panic 时的栈展开或终止 1.1 使用 panic! 的 backtrace 2、用 Result 处理可恢复的错误 2.1 匹配不同的错误 2.2 失败时 panic 的简写&#xff1a;unwrap 和 expect 2.3 传播错误 错误是软件中不可否认的事实&#xff0c;所…

[足式机器人]Part2 Dr. CAN学习笔记-Advanced控制理论 Ch04-17 串讲

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-Advanced控制理论 Ch04-17 串讲

DWM1000 物理层

UWB 物理层 DW1000设备的物理层&#xff08;PHY&#xff09;参数 dwt_config_t config {2, /* 通道号&#xff0c;用于无线通信。 */DWT_PRF_64M, /* 脉冲重复频率&#xff0c;设置为64MHz。 */DWT_PLEN_1024, /* 前导码长度&#xff0c;设置为1024个时…