記事ディレクトリ
事例紹介
学校OAシステムの調達承認事項:要件は以下の通りです。
- 購入者が教材を購入する
- 金額が 5000 以下 (0<x<=5000) の場合、教育長によって承認されます。
- 金額が 10,000 以下 (5,000<x<=10,000) の場合、学部長によって承認されます。
- 金額が 30,000 以下 (10,000<x<=30,000) の場合、副社長によって承認されます。
- 金額が30,000を超える場合(30,000<x)、本人の承認が必要となります。
従来の達成方法
異なる承認者に対応したクラスを作成し、クライアントにif else判定プログラムを記述し、条件が異なる場合に異なる承認者に処理させます。
分析する
クライアントは、分岐判断(スイッチなど)を使用してさまざまな購入リクエストを処理するため、次のような問題があります。
- 各レベルの担当者によって承認された金額が変更された場合、クライアント コードも変更する必要があります。
- クライアントは、承認レベルがいくつあるか、およびそれらにアクセスする方法を明確に知っている必要があります。このように、購入リクエストを処理するクラスと承認者の間には強い結合関係があり、コードの拡張やメンテナンスにはつながりません。
【改善する】
責任連鎖パターンを使用する
導入
基本的な紹介
- 責任の連鎖パターン (責任の連鎖パターンとも呼ばれます) は、リクエストの受信者オブジェクトの連鎖を作成します (以下の簡単な図を参照)。このパターンは、リクエストの送信者と受信者を分離します。送信者がリクエストを送信すると、受信者は、責任の連鎖の順序に従って、リクエストの処理を誰が担当するかを 1 つずつ見つけます。
- 責任連鎖パターンは通常、各受信者に別の受信者への参照が含まれます。オブジェクトがリクエストを処理できない場合は、同じリクエストを次の受信者に渡し、以下同様に処理できる人がいない場合は、最終的にプロンプトを表示するか、処理できないことを示すエラーを報告します。
- このタイプのデザインパターンは動作パターンです
キャラクター
Handler(抽象处理者)
: リクエストを処理するためのインターフェースを定義し、他のハンドラーを保存するための変数も備えています。ConcreteHandler(具体处理者)
: 担当するリクエストを処理するために、その後続プロセッサ (つまり、次のプロセッサ) にアクセスできます。現在のリクエストが処理できる場合は、そのリクエストが処理されます。それ以外の場合、リクエストは処理のために後続プロセッサに渡されます。責任の連鎖を形成します。Request(请求对象)
: リクエストを表す多くの属性が含まれますClient(请求者)
:リクエストを送信
アプリケーションシナリオ
- 同じリクエストを処理できるオブジェクトが複数ある場合(マルチレベルのリクエスト、休暇/昇給などの承認プロセス、Java Web での Tomcat のエンコーディング処理、インターセプタなど)
事例の実装
ケースその1
クラス図
成し遂げる
【購入依頼】
package com.atguigu.responsibilitychain;
/**
* 请求类
*/
public class PurchaseRequest {
/**
* 请求类型
*/
private int type = 0;
/**
* 请求金额
*/
private float price = 0.0f;
private int id = 0;
/**
* 构造器
* @param type
* @param price
* @param id
*/
public PurchaseRequest(int type, float price, int id) {
this.type = type;
this.price = price;
this.id = id;
}
public int getType() {
return type;
}
public float getPrice() {
return price;
}
public int getId() {
return id;
}
}
[抽象リクエストハンドラ]
package com.atguigu.responsibilitychain;
/**
* 请求处理者
*/
public abstract class Approver {
/**
* 下一个处理者
*/
Approver approver;
/**
* 当前处理者名字
*/
String name;
public Approver(String name) {
this.name = name;
}
/**
* 下一个处理者
* @param approver
*/
public void setApprover(Approver approver) {
this.approver = approver;
}
/**
* 处理审批请求的方法,得到一个请求, 处理是子类完成,因此该方法做成抽象
* @param purchaseRequest
*/
public abstract void processRequest(PurchaseRequest purchaseRequest);
}
【部門レベル担当者】
package com.atguigu.responsibilitychain;
public class DepartmentApprover extends Approver {
public DepartmentApprover(String name) {
super(name);
}
@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if(purchaseRequest.getPrice() <= 5000) {
System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
}else {
approver.processRequest(purchaseRequest);
}
}
}
【大学レベルのプロセッサー】
package com.atguigu.responsibilitychain;
public class CollegeApprover extends Approver {
public CollegeApprover(String name) {
super(name);
}
@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if(purchaseRequest.getPrice() < 5000 && purchaseRequest.getPrice() <= 10000) {
System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
}else {
approver.processRequest(purchaseRequest);
}
}
}
【副学長】
package com.atguigu.responsibilitychain;
public class ViceSchoolMasterApprover extends Approver {
public ViceSchoolMasterApprover(String name) {
super(name);
}
@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if(purchaseRequest.getPrice() < 10000 && purchaseRequest.getPrice() <= 30000) {
System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
}else {
approver.processRequest(purchaseRequest);
}
}
}
【校長】
package com.atguigu.responsibilitychain;
public class SchoolMasterApprover extends Approver {
public SchoolMasterApprover(String name) {
super(name);
}
@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if(purchaseRequest.getPrice() > 30000) {
System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
}else {
approver.processRequest(purchaseRequest);
}
}
}
【クライアント】
package com.atguigu.responsibilitychain;
public class Client {
public static void main(String[] args) {
//创建一个请求
PurchaseRequest purchaseRequest = new PurchaseRequest(1, 31000, 1);
//创建相关的审批人
DepartmentApprover departmentApprover = new DepartmentApprover("张主任");
CollegeApprover collegeApprover = new CollegeApprover("李院长");
ViceSchoolMasterApprover viceSchoolMasterApprover = new ViceSchoolMasterApprover("王副校");
SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("佟校长");
//需要将各个审批级别的下一个设置好 (处理人构成环形)
departmentApprover.setApprover(collegeApprover);
collegeApprover.setApprover(viceSchoolMasterApprover);
viceSchoolMasterApprover.setApprover(schoolMasterApprover);
//防止一千块钱也来找校长处理,找校长的话,校长就去找手下处理
schoolMasterApprover.setApprover(departmentApprover);
//找谁开始调用都可以实现功能
departmentApprover.processRequest(purchaseRequest);
viceSchoolMasterApprover.processRequest(purchaseRequest);
}
}
【主なカテゴリー】
请求编号 id= 1 被 佟校长 处理
请求编号 id= 1 被 佟校长 处理
Process finished with exit code 0
ケース2
クラス図
成し遂げる
【質問の種類】
package com.atguigu.responsibilitychain.Sample;
public class Trouble {
/**
* 问题编号
*/
private int number;
/**
* 根据编号生成问题
* @param number
*/
public Trouble(int number) {
this.number = number;
}
/**
* 获取问题编号
* @return
*/
public int getNumber() {
return number;
}
/**
* 代表问题的字符串
* @return
*/
public String toString() {
return "[Trouble " + number + "]";
}
}
[問題を解決するために使用される抽象クラス]
package com.atguigu.responsibilitychain.Sample;
public abstract class Support {
/**
* 解决问题的实例的名字
*/
private String name;
/**
* 要推卸给的对象
*/
private Support next;
/**
* 生成解决问题的实例
*
* @param name
*/
public Support(String name) {
this.name = name;
}
/**
* 设置要推卸给的对象
*
* @param next
* @return
*/
public Support setNext(Support next) {
this.next = next;
return next;
}
/**
* 解决问题的步骤
* 调用了抽象方法resolve,这里属于模板方法模式
* @param trouble
*/
public void support(Trouble trouble) {
if (resolve(trouble)) {
done(trouble);
} else if (next != null) {
// 问题还没有解决,让下一个对象来尝试解决
next.support(trouble);
} else {
// 所有对象都没有办法解决该问题,提示用户 问题没办法解决
fail(trouble);
}
}
/**
* 显示字符串
*
* @return
*/
public String toString() {
return "[" + name + "]";
}
/**
* 解决问题的方法
* 需要子类去具体实现,如果解决了问题,就返回true
*
* @param trouble
* @return
*/
protected abstract boolean resolve(Trouble trouble);
/**
* 解决
*
* @param trouble
*/
protected void done(Trouble trouble) {
System.out.println(trouble + " is resolved by " + this + ".");
}
/**
* 未解决
*
* @param trouble
*/
protected void fail(Trouble trouble) {
System.out.println(trouble + " cannot be resolved.");
}
}
【問題が解決しないカテゴリー】
package com.atguigu.responsibilitychain.Sample;
public class NoSupport extends Support {
public NoSupport(String name) {
super(name);
}
/**
* 解决问题的方法
* 什么都解决不了,直接返回false
* @param trouble
* @return
*/
protected boolean resolve(Trouble trouble) {
return false;
}
}
【特定の問題解決カテゴリ:問題数が制限以下であれば解決可能】
package com.atguigu.responsibilitychain.Sample;
/**
* 可以解决编号小于limit的问题
*/
public class LimitSupport extends Support {
private int limit;
/**
* 构造函数
*
* @param name
* @param limit
*/
public LimitSupport(String name, int limit) {
super(name);
this.limit = limit;
}
protected boolean resolve(Trouble trouble) {
if (trouble.getNumber() < limit) {
/*
解决了什么什么问题
*/
return true;
} else {
return false;
}
}
}
【具体的な解く問題の種類:問題番号が奇数であれば解けます】
package com.atguigu.responsibilitychain.Sample;
public class OddSupport extends Support {
/**
* 构造函数
* @param name
*/
public OddSupport(String name) {
super(name);
}
protected boolean resolve(Trouble trouble) {
if (trouble.getNumber() % 2 == 1) {
return true;
} else {
return false;
}
}
}
【特定問題解きクラス:指定された番号の問題のみを解くことができます】
package com.atguigu.responsibilitychain.Sample;
public class SpecialSupport extends Support {
private int number;
/**
* 构造函数
*
* @param name
* @param number
*/
public SpecialSupport(String name, int number) {
super(name);
this.number = number;
}
/**
* 解决问题的方法
*
* @param trouble
* @return
*/
protected boolean resolve(Trouble trouble) {
if (trouble.getNumber() == number) {
return true;
} else {
return false;
}
}
}
【主なカテゴリー】
package com.atguigu.responsibilitychain.Sample;
public class Main {
public static void main(String[] args) {
// 生成六个解决问题的实例
Support alice = new NoSupport("Alice");
Support bob = new LimitSupport("Bob", 100);
Support charlie = new SpecialSupport("Charlie", 429);
Support diana = new LimitSupport("Diana", 200);
Support elmo = new OddSupport("Elmo");
Support fred = new LimitSupport("Fred", 300);
// 连接对象,形成职责链
alice.setNext(bob).setNext(charlie).setNext(diana).setNext(elmo).setNext(fred);
// 制造各种问题,增长步长大一点,让问题的编号更加的分散
for (int i = 0; i < 500; i += 33) {
alice.support(new Trouble(i));
}
}
}
【走る】
[Trouble 0] is resolved by [Bob].
[Trouble 33] is resolved by [Bob].
[Trouble 66] is resolved by [Bob].
[Trouble 99] is resolved by [Bob].
[Trouble 132] is resolved by [Diana].
[Trouble 165] is resolved by [Diana].
[Trouble 198] is resolved by [Diana].
[Trouble 231] is resolved by [Elmo].
[Trouble 264] is resolved by [Fred].
[Trouble 297] is resolved by [Elmo].
[Trouble 330] cannot be resolved.
[Trouble 363] is resolved by [Elmo].
[Trouble 396] cannot be resolved.
[Trouble 429] is resolved by [Charlie].
[Trouble 462] cannot be resolved.
[Trouble 495] is resolved by [Elmo].
Process finished with exit code 0
Spring での責任連鎖パターンの適用
- springmvcリクエストのフローチャートではインターセプタ関連のメソッドinterceptor.preHandlerなどが実行されており、SpringMvcリクエストの処理時には責任連鎖モードとアダプタモードが使用されます。
- HandlerExecutionChain は主にリクエスト インターセプタの実行とリクエスト処理を担当しますが、リクエスト自体の処理は行わず、チェーン上に登録されているプロセッサにリクエストを割り当てて実行するだけです。これは責任の連鎖の実装方法です。これにより、責任の連鎖自体と処理ロジックの間のギャップが減少します。カップリングにより処理フローが標準化されます。
- HandlerExecutionChain は、対応するインターセプタを登録できる HandlerInterceptor のコレクションを維持します。
要約する
【アドバンテージ】
- リクエストと処理を分離して分離を実現し、システムの柔軟性を向上させる
- チェーンの構造を知る必要がないように単純化されたオブジェクト
- チェーンの構造は動的に調整可能
- 全員が
ConcreteHandler
自分の仕事にもっと集中できるようにすると、プログラムのロジックがより明確になります
【欠点】
- 特にチェーンが比較的長い場合、パフォーマンスが低下します。したがって、チェーン内の最大ノード数を制御する必要があります。通常、ハンドラーに最大ノード数を設定します。 setNext() メソッドで、しきい値を超えてチェーンが終了していないかどうかを判断します。システムのパフォーマンスを損なう過度に長いチェーンを避けるために確立することが許可されています。
- デバッグが不便、再帰的な手法を使用しているため、デバッグ時にロジックが複雑になる可能性があります。
【Q&A】
ウィンドウ システムでは、責任連鎖パターンがよく使用されます。ウィンドウ システムのウィンドウには、ボタン、テキスト入力ボックス、チェック ボックス、その他のコンポーネント (コンポーネントまたはコントロールとも呼ばれます) があります。マウスがクリックされると、マウス クリック イベントの処理はどのように伝播されますか? 責任の連鎖パターンの次のコンポーネント (転送先のオブジェクト) はどれですか?
回答: 通常、次のフィールドに保存されるのはコントロールの親ウィンドウです。
以下の小さなダイアログ ボックスに示されているように。「フォント」リストボックスにフォーカスが移動したら、キーボードの↑↓キーを押して、対応するフォントを選択します。ただし、「バランスフォントを表示」チェックボックスにフォーカスを移動した状態で↑キーを押すと、「フォント」リストボックスにフォーカスが移動し、その後↓キーを押してもフォーカスは移動しません。チェックボックスに戻ります。この問題を説明するには、責任連鎖モデルの思考法を使用してください。
フォーカスがリスト ボックスにある場合、リスト ボックスは ↑↓ キーが押されたときのイベントを単独で処理し、リクエストを次へオフロードしませんが、チェック ボックスにフォーカスがある場合は処理しません。 ↑↓ キーが押されたときのイベントですが、リクエストは次のダイアログ ボックスに対応する親ダイアログ ボックスにオフロードされます。親ダイアログボックスは、↑↓キーが押されたイベントを受け取ると、リストボックスにフォーカスを移動します。
記事の説明
- この記事は、シャン シリコン バレーを勉強するための私の学習ノートです。記事のほとんどの内容は、シャン シリコン バレーのビデオ (クリックしてシャン シリコン バレー関連コースを学習する) から引用していますが、一部の内容は私自身の考えによるものです。他の人がより便利に勉強できるように記事を作成します。自分のメモを整理したり、記事を通じて関連する知識を直接学習したりできます。侵害がある場合は、削除するためにご連絡ください。最後に、Shang Silicon Valley に感謝の意を表したいと思います。質の高いコース。
- また、『グラフィックデザインパターン』(グラフィックデザインパターン/結城宏著、楊文宣訳、北京:人民郵政通信社、2017.1)という書籍も同時に読み、両者の内容を統合して知識を作りました。ポイントをより包括的に。