이 기사에서는 golang 디자인 패턴에 대한 관련 지식을 제공합니다. 주로 책임 체인 패턴이 무엇인지, 그 가치는 물론 책임 체인 Go 코드의 구체적인 구현 방법을 함께 살펴보시기 바랍니다. 도움이 필요한 친구들이 도움이 될 것입니다.
오늘은 디자인 패턴과 관련된 기사를 계속 업데이트하겠습니다. 템플릿 패턴과 전략 패턴에 대한 이전 두 기사에서 제가 요약한 "폭력 이론"을 말씀드렸습니다. "세 가지 디자인 패턴: 템플릿, 전략 및 체인 복잡하고 변화무쌍한 비즈니스 시스템 프로세스의 문제점을 해결하는 강력한 도구입니다". 이번 글에서는 세 번째 디자인 패턴 도구인 책임 사슬 패턴에 대해 이야기해 보겠습니다.
Chain of Responsibility - 영어 이름인 Chain of Responsibility는 때때로 책임 체인 모델로 번역됩니다. 인터넷에는 책임 사슬이라는 이름이 더 많이 있지만 여기서는 모두가 같은 이름이라는 것을 모두 알고 있습니다.
행동 디자인 패턴입니다. 이 패턴을 사용하면 요청에 대한 여러 프로세서로 구성된 링크를 만들 수 있습니다. 각 프로세서는 자신의 책임을 담당하며 서로 연결되지 않습니다. 자체 작업을 완료한 후 요청 개체가 링크의 다음 개체로 전달됩니다. . 처리용 프로세서.
책임 체인은 많은 인기 프레임워크에서 사용됩니다. 미들웨어 및 인터셉터와 같은 프레임워크 구성 요소는 모두 이 두 구성 요소를 더 자주 사용해야 합니다. 웹 인터페이스를 개발할 때 액세스 로그 기록, 토큰 구문 분석, 인터페이스 응답의 통일된 구조 형식화 등 유사한 프로젝트 공통 작업이 모두 미들웨어 및 인터셉터에서 완료되므로 이러한 기본 작업이 인터페이스와 통합될 수 있습니다. 분리됨.
미들웨어와 인터셉터의 구성 요소는 프레임워크에 의해 설계되었으며 직접 삽입될 수 있습니다. 오늘 기사에서 이야기할 내용은 책임 사슬을 핵심 비즈니스 프로세스 설계에 적용하는 방법입니다. 기본적인 공개 작업만 수행하세요.
위에서 우리는 프로젝트의 공개 구성 요소에서 책임 사슬의 일부 적용에 대해 이야기했으며 이를 통해 사전 및 사후 프로세스에 몇 가지 기본적인 공통 기능을 추가할 수 있었습니다. 핵심 논리의. 그러나 실제로 일부 핵심 비즈니스에서는 책임 사슬 모델을 적용하면 비즈니스 프로세스 단계를 고통 없이 확장할 수 있습니다.
예를 들어 타오바오가 처음 설립되었을 때, 쇼핑 주문 처리 과정은 처음에는 이랬을 것입니다.
책임 사슬 모델 - 쇼핑 주문 - 순수 버전
전체 프로세스가 비교적 깔끔합니다. "사용자 매개변수 확인 - 장바구니 데이터 확인 - 제품 재고 확인 - 화물 계산 - 재고 차감 - 주문 생성" , Let's 이를 쇼핑 주문 프로세스의 순수 버전이라고 부릅니다. 이는 일반적으로 제품이 0에서 1로 바뀔 때 발생합니다. 온라인 쇼핑의 경우 프로세스는 비교적 순수합니다. 제품을 선택하고, 주문하고, 온라인으로 결제하면 됩니다.
하지만 우리는 모두 인터넷 서핑의 베테랑이고 모두가 그것에 대해 잘 알고 있습니다. 이 프로세스가 너무 순수하게 유지된다면 회사의 PM과 운영은 떠날 수 있습니다. 쇼핑 웹사이트가 운영되고 소비자가 생긴 후에는 판매를 늘리기 위해 일반적으로 특정 상품 카테고리에 대한 전액 할인과 같은 일부 프로모션 방법이 추가됩니다.
운영은 가만히 있을 수 없고, 고객에 대해 더 많이 이야기하고, 쇼핑 페스티벌을 만들고, 쿠폰을 마련하여 더 많은 사용자를 유치할 수 없습니다. 이와 같이 주문 과정에서 장바구니에 담긴 상품이 할인 조건을 충족하는지, 사용자가 쿠폰을 보유하고 있는지 여부를 판단해야 하며, 그렇다면 금액을 삭감하거나 축소할 수 있다. 이는 내부 주문 프로세스 중간에 두 개의 하위 프로세스를 추가하는 것과 같습니다.
Chain of Responsibility Model - Shopping Order - Experienced Version
새로 추가된 로직을 구현하려면 서면 주문 프로세스에 최소 2개의 if else 분기를 추가하여 이 두 로직을 추가해야 합니다. 하지만 가장 끔찍한 점은전체 프로세스가 결합되어 있기 때문에 수정 후에는 전체 프로세스를 테스트해야 한다는 것입니다. 그리고 위의 경험을 통해 우리는 이 프로세스가 앞으로 확실히 확장될 것이라는 점도 알아야 합니다. 예를 들어, 앞으로 주문 생성 프로세스에 단계를 추가할 때마다 커뮤니티 컷팅 및 주문과 같은 기능을 추가할 예정입니다. 이미 작성된 단계를 수정해야 합니다. 코드가 두렵습니까?
어떤 친구들은 인터넷 전자상거래 쇼핑의 과정이 정말 다양하고 회사마다 과정이 다르다고 말할 수도 있습니다. 환자가 의사를 만나러 병원에 가는 또 다른 예를 들어보겠습니다. 환자가 의사를 만나기 위한 기본 단계는 다음과 같습니다.
등록 -> 진료소에서 의사 만나기 -> 담당 진료소에서 지불 - >약국에서 약 구입하기
단, 실험실 검사, 영상 촬영 등이 필요한 환자가 있을 수 있습니다. 병원에서의 진료 과정은 다음과 같을 수 있습니다. >사무실 결제—>약국에서 약을 구입하세요
그래서 진료 과정도 환자의 상태에 따라 더 많은 단계를 거치게 됩니다.
이제 다음을 확인할 수 있습니다.프로세스의 단계가 고정되어 있지 않은 경우 원래 개발 및 테스트된 프로세스를 수정하지 않고 프로세스에 단계를 추가하려면 전체 프로세스에서 다양한 단계를 분리해야 합니다. 프로세스의 확장성을 위해 책임 사슬 모델을 사용할 수 있습니다. 이 모델을 사용하면 먼저 프로세스 링크의 단계를 설정한 다음 이를 실행할 수 있습니다
.
Member 속성
nextHandler
: 호출을 기다리는 다음 객체 인스턴스 SetNext
: 다음 개체의 인스턴스를 현재 개체의 nextHandler
속성에 바인딩합니다. nextHandler
: 下一个等待被调用的对象实例SetNext
: 把下一个对象的实例绑定到当前对象的nextHandler
属性上;Do
: 当前对象业务逻辑入口,他是每个处理对象实现自己逻辑的地方;Execute
: 负责职责链上请求的处理和传递;它会调用当前对象的Do
,nextHandler
不为空则调用nextHandler.Do
;如果抽象成 UML 类图表示的话,差不多就是下面这个样子。
定义了一个职责链模式处理对象的接口Handler
,由ConcreteHandler
–具体处理对象的类型来实现。
观察上图以及上面对象特性的分析,其实是能看出 SetNext
和 Execute
这两个行为是每个 ConcreteHandler
都一样的,所以这两个可以交给抽象处理类型来实现,每个具体处理对象再继承抽象类型,即可减少重复操作。
所以责任链模式的抽象和提炼可以进化成下图这样:
了解完职责链模式从接口和类型设计上应该怎么实现后,我们进入代码实现环节,职责链模式如果用纯面向对象的语言实现起来还是很方便的,把上面的UML类图直接翻译成接口、抽象类,再搞几个实现类就完事。
想把上面这个UML类图翻译成Go代码还是有点难度的。这里咱们提供一个用 Go 实现职责链模式完成医院就诊流程的代码示例。
虽然 Go 不支持继承,不过我们还是能用类型的匿名组合来实现,下面以病人去医院看病这个处理流程为例提供一个具体示例。
看病的具体流程如下:
挂号—>诊室看病—>收费处缴费—>药房拿药
我们的目标是利用责任链模式,实现这个流程中的每个步骤,且相互间不耦合,还支持向流程中增加步骤。
先来实现职责链模式里的公共部分—即模式的接口和抽象类
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
类型充当了模式中抽象类的角色,关于这个Next
Do
: 각 처리 개체가 자체 논리를 구현하는 현재 개체 비즈니스 논리 항목입니다.
Execute
: 요청 처리 및 전달을 담당합니다. 책임 체인이 호출됩니다. 현재 객체의 Do
및 nextHandler
가 비어 있지 않으면 nextHandler.Do
를 호출하세요. ul> ul>🎜UML 클래스 다이어그램으로 추상화하면 다음과 같습니다. 🎜🎜🎜🎜는 특정 처리 개체 유형인 ConcreteHandler
에 의해 구현되는 책임 모드 처리 개체 체인에 대한 인터페이스 Handler
를 정의합니다. 🎜🎜위 그림과 위의 객체 특성 분석을 관찰하면 실제로 SetNext
및 Execute
의 두 가지 동작이 각 ConcreteHandler 동일하므로 이 둘은 추상 처리 유형으로 구현될 수 있으며, 각 특정 처리 객체는 반복 작업을 줄이기 위해 추상 유형을 상속합니다. 🎜🎜그래서 책임 사슬 모델의 추상화와 개선은 다음 그림으로 발전할 수 있습니다: 🎜🎜<img src="https://img.php.cn/upload/article/000/000/020/2825a64757aefc1a1e4a326bf16d28e4-3.%20png" alt="당신도 비즈니스 개발자이신가요? 추가 제품 요구 사항을 방지하려면 이 디자인 패턴을 미리 사용하세요.">🎜🎜인터페이스 및 유형 디자인 측면에서 책임 사슬 모델을 어떻게 구현해야 하는지 이해한 후 코드 구현 단계 책임 체인을 사용하는 경우 순수한 객체 지향 언어를 구현하는 것이 여전히 매우 편리합니다. 위의 UML 클래스 다이어그램을 인터페이스와 추상 클래스로 직접 변환한 다음 몇 가지 구현 클래스를 생성하면 됩니다. 완료. 🎜🎜위의 UML 클래스 다이어그램을 Go 코드로 변환하는 것은 아직 조금 어렵습니다. 여기에서는 병원 치료 프로세스를 완료하기 위해 Go를 사용하여 책임 사슬 모델을 구현하는 코드 예제를 제공합니다. 🎜🎜<a name="<strong>%E8%81%8C%E8%B4%A3%E9%93%BE%20Go%20%E4%BB%A3%E7%A0%81%E5%AE%%209E%E7%8E%B0</strong>">🎜🎜🎜🎜책임 체인 Go 코드 구현🎜🎜🎜Go는 상속을 지원하지 않지만 익명 유형 조합을 사용하여 이를 구현할 수 있습니다. 의사를 만나러 병원에 가는 경우 처리 흐름을 예로 들어 구체적인 예를 제시합니다. 🎜🎜의사를 만나는 구체적인 과정은 다음과 같습니다. 🎜🎜🎜등록 -> 진료소에서 의사 만나기 -> 수수료 사무실에서 지불 ->약국에서 약 구입🎜🎜🎜우리의 목표는 이 프로세스의 모든 단계를 실현하기 위한 책임 사슬 모델은 서로 결합되지 않으며 프로세스에 단계를 추가하는 것을 지원합니다. 🎜🎜먼저 책임 사슬 패턴의 공개 부분, 즉 패턴의 인터페이스와 추상 클래스를 구현해 보겠습니다.🎜<pre class="brush:php;toolbar:false">//流程中的请求类--患者type patient struct {
Name string
RegistrationDone bool
DoctorCheckUpDone bool
MedicineDone bool
PaymentDone bool}</pre>🎜위 코드의 <code>Next
유형은 패턴에서 추상 클래스 역할을 합니다. . 이 정보 다음
유형이 여기서 강조 표시됩니다. 🎜在我们的职责链的UML图里有说明Do
方法是一个抽象方法,留给具体处理请求的类来实现,所以这里Next
类型充当抽象类型,只实现公共方法,抽象方法留给实现类自己实现。并且由于 Go 并不支持继承,即使Next
实现了Do
方法,也不能达到在父类方法中调用子类方法的效果—即在我们的例子里面用Next
类型的Execute
方法调用不到外部实现类型的Do
方法。
所以我们这里选择Next
类型直接不实现Do
方法,这也是在暗示这个类型是专门用作让实现类进行内嵌组合使用的。
接下来我们定义职责链要处理的请求,再回看一下我们的UML图,实现处理逻辑和请求传递的Do
、Execute
方法的参数都是流程中要处理的请求。这里是医院接诊的流程,所以我们定义一个患者类作为流程的请求。
//流程中的请求类--患者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视频教程》
위 내용은 강력한 디자인 패턴 도구인 '책임 사슬 모델(Chain of Responsibility Model)”(go 구현 프로세스 포함)에 대해 자세히 이야기해 보겠습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!