この記事では、Golang のデザイン パターンに関する関連知識をお届けします。責任チェーン パターンとは何か、その価値、責任チェーン Go コードの具体的な実装方法を主に紹介します。一緒に見ていきましょう。それは困っている人たちを助けます。
今日もデザイン パターンに関する記事を更新していきます。テンプレート パターンと戦略パターンに関する前の 2 つの記事で、「テンプレート、戦略、そして責任連鎖の 3 つの設計パターンは、複雑で変化しやすいビジネス システム プロセスの問題点を解決する強力なツールです。」この記事では、3 番目のデザイン パターン ツールである責任連鎖パターンについて説明します。
。 たとえば、タオバオが設立された当初、買い物注文の処理プロセスは最初は次のようなものだったと思われます。
責任連鎖モデル - 買い物注文 - 純粋バージョン
プロセス全体は比較的クリーンです
「ユーザー パラメーターの検証 - ショッピング カート データの検証 -」 「製品在庫の確認 – 運賃計算 – 在庫控除 – 注文生成」、これをショッピング注文プロセスの純粋バージョンと呼びましょう。通常、これは製品が 0 から 1 になるときです。このプロセスは比較的純粋でオンラインです。ショッピングでは、商品を選択し、注文し、オンラインで支払うだけです。 しかし、私たちは皆、インターネット サーフィンのベテランであり、インターネット サーフィンのことに精通しています。このプロセスがあまりにも純粋なままであれば、会社の PM と運用部門が去ってしまう可能性があります。ショッピング ウェブサイトが稼働し、消費者が増えた後は、通常、売上を増やすために、特定のカテゴリの商品の全額割引などのプロモーション方法が追加されます。
運営も怠けてはいけません。顧客についてもっと話し、買い物祭りを作り、より多くのユーザーを引き付けるためにクーポンを手配します。このように、注文の際には、ショッピングカート内の商品が割引条件を満たしているか、クーポンを持っているか、クーポンを持っていれば値引きや減額が可能かを判断する必要があります。これは、社内の発注プロセスの途中に 2 つのサブプロセスを追加することに相当します。
責任連鎖モデル - 買い物注文 - 体験版
新しく追加されたロジックを実装するには、少なくとも注文プロセスを作成する必要があります。 2 つの if else 分岐を追加すると、これら 2 つのロジックを追加できます。しかし、最も恐ろしいことは、
プロセス全体が結合されているため、変更後にプロセス全体をテストする必要があるということです。上記の経験から、このプロセスは将来確実に拡張されることも知っておく必要があります。たとえば、コミュニティカットや注文などの機能を追加します。将来、注文生成プロセスにステップを追加するたびに、すでに書かれた手順を変更する必要がありますが、コードが怖いですか? 友人の中には、インターネット電子商取引のショッピング プロセスは確かに非常に多様であり、各企業のプロセスも異なると言う人もいるかもしれません。患者が医師の診察を受けるために病院に行く別の例を考えてみましょう。一般的に、患者が医師の診察を受けるための基本的な手順は次のとおりです:
登録—>クリニックで医師の診察—>料金所で支払う—>薬局で薬を受け取る しかし、検査やフィルムなどが必要な患者もいるかもしれません。病院での治療のプロセスは次のとおりです: 登録—>初回診察—>ラジオ画像撮影診療科→再診室→担当窓口で支払い→薬局で薬を受け取る そのため、患者様の状況に応じて診療の流れも多くなります。状態。 これで確信できます: プロセスのステップが修正されていない場合、元の開発およびテストされたプロセスを変更せずにプロセスにステップを追加するには、プロセス全体を分離する必要があります。プロセスのスケーラビリティを高めるためのさまざまなステップ。この場合、責任連鎖モデルを使用できます。このモデルでは、最初にプロセス リンクにステップを設定してから、 を実行できます。 責任連鎖を設計するように求められた場合、どのようにするかそれをデザインすべきでしょうか?どのようなメソッドを提供および実装する必要がありますか?プロセス内のステップを接続するためにそれをどのように使用しますか?ここでは、責任連鎖モデルを使用して、医師の診察を受けてデモンストレーションを行うというシナリオのプロセス ステップを実装します。ショッピングと注文のプロセスは似ています。自分で実装してみることができます。まず、構造を学びます。責任連鎖モデルを理解して、いくつかの模擬例を作成し、それをマスターしたら、ビジネス上の問題を解決するためにそれを使用してみることができます。 まず第一に、上記のプロセス拡張の問題点について考えることができます。プロセスの各ステップは、論理的な抽象化を完了するために処理オブジェクトによって完了する必要があります。すべての処理オブジェクトは、統一されたメソッドを提供する必要があります。独自のロジックを処理します。次に、次の処理オブジェクトへの参照も維持する必要があります。現在のステップ自身のロジックが処理された後、次のオブジェクトの処理メソッドが呼び出され、リクエストは処理のために後続のオブジェクトに渡されます。 、プロセスが終了するまで順番に処理が進みます。 要約すると、責任連鎖パターンを実装するオブジェクトには、少なくとも次の特性が含まれている必要があります: Handler SetNext 責任連鎖パターンがどのように実装されるべきかを理解した後、インターフェイスと型の設計の観点から 最後に、コードの実装フェーズに入ります。責任連鎖モデルは、純粋なオブジェクト指向言語で実装するのに非常に便利です。上記の UML クラス図をインターフェイスと抽象クラスに直接変換するだけです。いくつかの実装クラスを作成すれば完了です。 上記の UML クラス図を Go コードに変換するのはまだ少し難しいです。ここでは、Go を使用して責任連鎖モデルを実装し、病院の治療プロセスを完了するコード例を示します。 登録—>クリニックでの診察—>診療所での支払い—>病院で薬を受け取る薬局
責任連鎖モデルを使用してプロセスを実装する
UML クラス図に抽象化すると、次のようになります。
責任モード処理オブジェクトのチェーンのインターフェイス nextHandler
: 次の待機 呼び出されたオブジェクト インスタンス
SetNext
: 次のオブジェクト インスタンスを現在のオブジェクトの nextHandler
Attributes; Do
: 現在のオブジェクトのビジネス ロジック エントリ (各処理オブジェクトが独自のロジックを実装する場所); Execute
: 責任者責任チェーン上のリクエストの処理と配信のために、現在のオブジェクトの Do
を呼び出します。nextHandler
が空でない場合は、nextHandler.Do## を呼び出します。 #;
を定義します。これは、特定の処理のタイプである
ConcreteHandler によって実装されます。物体。
と
Execute の 2 つの動作がそれぞれ
ConcreteHandler# であることがわかります。 ## これらはすべて同じであるため、これら 2 つは抽象処理型で実装でき、各特定の処理オブジェクトは抽象型を継承して繰り返し操作を減らします。 したがって、責任連鎖パターンの抽象化と洗練は、次の図に発展する可能性があります。
責任連鎖 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 型コードは pattern 内の抽象クラスの役割として機能します。ここでは、この
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视频教程》
以上が強力なデザインパターンツールである「責任連鎖モデル」について詳しく話しましょう(Go実装プロセス付き)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。