Home  >  Article  >  Backend Development  >  Let's talk in depth about the 'Chain of Responsibility Model”, a powerful design pattern tool (with go implementation process)

Let's talk in depth about the 'Chain of Responsibility Model”, a powerful design pattern tool (with go implementation process)

藏色散人
藏色散人forward
2023-01-17 11:43:371725browse

This article brings you relevant knowledge about golang design patterns. It mainly introduces what the responsibility chain pattern is and its value, as well as the specific implementation method of the responsibility chain Go code. Let’s take a look at it together. Hope it helps those in need.

I will continue to update articles related to design patterns today. In the previous two articles about template patterns and strategy patterns, I told you a "violent theory" that I summarized: "Templates, strategies and The three design patterns of chain of responsibility are a powerful tool to solve the pain point of complex and changeable business system processes." In this article, let’s talk about the third design pattern tool—the chain of responsibility pattern.

Chain of responsibility model

Chain of responsibility——The English name Chain of responsibility is sometimes translated into chain of responsibility model . I see that there are more names on the Internet called chain of responsibility, but here everyone just knows that they are the same thing.

It is a behavioral design pattern. Using this pattern, we can create a link composed of multiple processors for requests. Each processor is responsible for its own responsibilities and is not coupled to each other. After completing its own tasks, the request object is passed to the next one in the link. processor for processing.

Chain of responsibility is used in many popular frameworks. Framework components such as middleware and interceptors all apply this design pattern. These two components should be used more often. When developing Web interfaces, similar project common operations such as recording access logs, parsing Tokens, and formatting the unified structure of interface responses are all completed in middleware and interceptors, so that these basic operations can be integrated with the interface. The business logic is decoupled.

The components of middleware and interceptors are designed for us by the framework and can be directly inserted into them. What we are going to talk about in today’s article is how to apply the chain of responsibility to our core business process design. , rather than just doing those basic public operations.

The value of chain of responsibility

Above we talked about some applications of chain of responsibility in the public components of the project, let We can add some basic common functions to the pre- and post-processes of the core logic. But in fact, in some core businesses,

applying the chain of responsibility model allows us to painlessly expand the steps of the business process.

For example, when Taobao was first established, the shopping order processing process may have been like this at first.

Lets talk in depth about the Chain of Responsibility Model”, a powerful design pattern tool (with go implementation process)

Chain of Responsibility Model—Shopping Order—Pure Version

The whole process is relatively clean

"User parameter verification-shopping cart data verification- Product inventory verification – freight calculation – inventory deduction – order generation”, let’s call it the pure version of the shopping order process. This is usually when the product goes from 0 to 1. The process is relatively pure and online. For shopping, you can just select products, place orders, and pay online.

But we are all veterans of Internet surfing, and we are all familiar with it. If this process remains so pure, the company's PM and operations can leave. After the shopping website is up and running and there are consumers, in order to increase sales, some promotion methods such as full discounts on certain categories of goods will usually be added.

Operations can’t be idle either, talk more about customers, create a shopping festival, and then arrange coupons to attract more users. In this way, during the process of placing an order, it is necessary to determine whether the products in the shopping cart meet the discount conditions, whether the user has a coupon, and if so, the amount can be reduced or reduced. It is equivalent to adding two sub-processes in the middle of our internal order process.

Lets talk in depth about the Chain of Responsibility Model”, a powerful design pattern tool (with go implementation process)

Chain of Responsibility Model—Place Shopping Order—Experienced Version

In order to implement the newly added logic, we have to at least write the order process Adding two if else branches can add these two logics. But the most terrible thing is that

because the entire process is coupled together, after modification, we have to test the entire process. And with the above experience, we should also know that this process will definitely be expanded in the future. For example, we will add functions such as community cutting and ordering. In the future, every time you add steps to the order generation process, you will have to modify the already written steps. Are you afraid of the code?

Some friends may say that the Internet e-commerce shopping process may indeed be quite diverse, and each company's process is different. Let’s take another example of a patient going to the hospital to see a doctor. Generally speaking, the basic steps for a patient to see a doctor are:

Registration—>Seeing a doctor in the clinic—>Paying at the charge office—>Getting medicine at the pharmacy

But there may be patients who need tests, films, etc. The process of their medical treatment in the hospital may be as follows:

Registration—>Initial consultation—>Radio imaging department—> ;Re-examination room—>Payment at the charge office—>Pick up medicine at the pharmacy

Therefore, the process of medical treatment will also have more steps depending on the patient’s condition.

Now we can be sure: If the steps of a process are not fixed, in order to add steps to the process without modifying the original developed and tested process, we need to make the entire process Decoupling the various steps to increase the scalability of the process. In this case, you can use the chain of responsibility model. This model allows us to first set the steps in the process link and then execute .

Use the responsibility chain model to implement the process

If we are asked to design the responsibility chain, how should we design it? What methods should be provided and implemented? How to use it to connect the steps in the process? Here we use the chain of responsibility model to implement the process steps in the scenario of seeing a doctor to give you a demonstration. The process of shopping and placing an order is similar. We can try to implement it ourselves. First, learn the structure of the chain of responsibility model and make some Mock examples. Once you have mastered it, you can try to use it to solve business problems.

First of all, we can think of the pain points of the above process expansion. Each step in the process should be completed by a processing object to complete the logical abstraction. All processing objects should provide a unified method of processing their own logic. Secondly, they should also Maintain a reference to the next processing object. After the current step's own logic is processed, the processing method of the next object is called, and the request is handed over to the subsequent object for processing, and the process proceeds in sequence until the end of the process.

To sum up, objects that implement the chain of responsibility pattern need to contain at least the following characteristics:

  • Member attributes
    • nextHandler: Next wait Called object instance
  • Member method
    • SetNext: Bind the next object instance to the current object’s nextHandlerAttributes;
    • Do: The current object business logic entry, which is where each processing object implements its own logic;
    • Execute : Responsible for the processing and delivery of requests on the responsibility chain; it will call Do of the current object. If nextHandler is not empty, it will call nextHandler.Do;

If abstracted into a UML class diagram, it would look like the following.

Lets talk in depth about the Chain of Responsibility Model”, a powerful design pattern tool (with go implementation process)

defines an interface Handler for chain of responsibility mode processing objects, which is implemented by ConcreteHandler – the type of specific processing object.

Observing the above picture and the analysis of the above object characteristics, we can actually see that the two behaviors of SetNext and Execute are each ConcreteHandler They are all the same, so these two can be implemented by abstract processing types, and each specific processing object inherits the abstract type to reduce repeated operations.

So the abstraction and refinement of the chain of responsibility pattern can evolve into the following figure:

Lets talk in depth about the Chain of Responsibility Model”, a powerful design pattern tool (with go implementation process)

After understanding how the chain of responsibility pattern should be implemented in terms of interface and type design Finally, we enter the code implementation phase. The chain of responsibility model is very convenient to implement in a pure object-oriented language. Just translate the above UML class diagram directly into interfaces and abstract classes, and then create a few implementation classes and you're done.

It is still a bit difficult to translate the above UML class diagram into Go code. Here we provide a code example that uses Go to implement the chain of responsibility model to complete the hospital treatment process.

Responsibility chain Go code implementation

Although Go does not support inheritance, we can still use anonymous combinations of types To achieve this, a specific example is provided below, taking the process of patients going to the hospital for medical treatment as an example.

The specific process of medical treatment is as follows:

Registration—>Seeing a doctor in the clinic—>Paying at the fee office—>Getting medicine at the pharmacy

Ours The goal is to use the chain of responsibility model to implement each step in this process without coupling to each other, and to support adding steps to the process.

First, let’s implement the common parts in the chain of responsibility pattern—that is, the interface and abstract class of the pattern

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}

The Next type in the above code acts as the role of the abstract class in the pattern , let’s focus on this Next type here.

在我们的职责链的UML图里有说明Do方法是一个抽象方法,留给具体处理请求的类来实现,所以这里Next类型充当抽象类型,只实现公共方法,抽象方法留给实现类自己实现。并且由于 Go 并不支持继承,即使Next实现了Do方法,也不能达到在父类方法中调用子类方法的效果—即在我们的例子里面用Next 类型的Execute方法调用不到外部实现类型的Do方法。

所以我们这里选择Next类型直接不实现Do方法,这也是在暗示这个类型是专门用作让实现类进行内嵌组合使用的。

接下来我们定义职责链要处理的请求,再回看一下我们的UML图,实现处理逻辑和请求传递的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}

处理器定义好了,怎么给用他们串成患者就诊这个流程呢?

func main() {
 receptionHandler := &Reception{}
 patient := &patient{Name: "abc"}
 // 设置病人看病的链路
 receptionHandler.SetNext(&Clinic{}).SetNext(&Cashier{}).SetNext(&Pharmacy{})
  receptionHandler.Execute(patient)}

上面的链式调用看起来是不是很清爽,嘿嘿别高兴太早,这里边有个BUG— 即Reception接诊挂号这个步骤提供的逻辑没有调用到,所以我们这里再定义个StartHandler 类型,它不提供处理实现只是作为第一个Handler向下转发请求

// StartHandler 不做操作,作为第一个Handler向下转发请求type StartHandler struct {
 Next}// Do 空Handler的Dofunc (h *StartHandler) Do(c *patient) (err error) {
 // 空Handler 这里什么也不做 只是载体 do nothing...
 return}

这也是Go 语法限制,公共方法Exeute并不能像面向对象那样先调用this.Do 再调用this.nextHandler.Do 具体原因咱们上边已经解释过了,如果觉得不清楚的可以拿Java实现一遍看看区别,再琢磨一下为啥Go里边不行。

所以整个流程每个环节都能被正确执行到,应该这样把处理类串起来。

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方法里加判断,一旦满足中止后就不再继续往链路的下级节点传递请求。Gin 的中间件的abort方法就是按照这个原理实现的,同时这也是职责链跟装饰器模式的一个区别,装饰器模式无法在增强实体的过程中停止,只能执行完整个装饰链路。

后面大家可以看看针对那些可能未来经常会变的核心业务流程,可以在设计初期就考虑使用职责链来实现,减轻未来流程不停迭代时不好扩展的痛点。当然职责链也不是万能的,对于那些固定的流程显然是不适合的。咱们千万不要手里拿着锤子就看什么都是钉子,所有的设计模式一定要用在合适的地方。

既然这里提到了装饰器,那么下一期就写写装饰器吧,不对,装饰器算是代理模式的一个特殊应用,那就还是先介绍代理未来再介绍装饰器吧,这样阅读体验会更好一些。

喜欢这系列文章的朋友们还请多多关注,转发起来吧。

推荐学习:《go视频教程

The above is the detailed content of Let's talk in depth about the 'Chain of Responsibility Model”, a powerful design pattern tool (with go implementation process). For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:learnku.com. If there is any infringement, please contact admin@php.cn delete