現実の責任連鎖パターン
責任の連鎖パターンの例を実際に見つけるのは難しくありません。責任の連鎖パターンに関連する 2 つの一般的なシナリオを次に示します。
❏ 朝のラッシュアワーにスムーズにバスに乗り込むことができれば、とても幸せな一日を過ごすことができるでしょう。バスは人が多いので、乗車しても車掌が見つからないことが多く、2元硬貨を前に渡さなければなりません。幸運にもあなたの前に立っている最初の人が車掌である場合を除いて、コインは通常、最終的に車掌に到達するまでに N 人の手を通過する必要があります。
❏ 朝のラッシュアワーにスムーズにバスに乗り込むことができれば、とても幸せな一日を過ごすことができるでしょう。バスは人が多いので、乗車しても車掌が見つからないことが多く、2元硬貨を前に渡さなければなりません。幸運にもあなたの前に立っている最初の人が車掌である場合を除いて、コインは通常、最終的に車掌に到達するまでに N 人の手を通過する必要があります。
注文クーポンを例に挙げます。
私たちが携帯電話を販売する電子商取引サイトを担当しているとします。予約金 500 元と保証金 200 元の 2 回の予約 (この時点で注文が発生しています) を経て、現在、正式購入。
同社は、デポジットを支払ったユーザーに対して特定の優遇ポリシーを設けています。正式購入後、500元のデポジットを支払ったユーザーはモールで100元のクーポンを受け取り、200元のデポジットを持ったユーザーは50元のクーポンを受け取り、以前にデポジットを支払っていないユーザーは、通常の購入モードのみに入ります。つまり、クーポンはなく、在庫が限られている場合に購入できる保証はありません。
私たちの注文ページは PHP が吐き出すテンプレートであり、ページの読み込みの開始時に、PHP はいくつかのフィールドをページに渡します。
❏ orderType: 注文タイプ(入金ユーザーまたは一般購入者)を示し、コード値が 1 の場合は 500 元の入金ユーザー、2 の場合は 200 元の入金ユーザー、3 の場合は入金ユーザーです。一般購入者です。
❏ 支払い: ユーザーがデポジットを支払ったかどうかを示します。値は true または false。ユーザーは 500 元のデポジットで注文を出しましたが、デポジットを支払っていない場合は、通常の購入にのみダウングレードできます。今モード。
❏在庫:通常の購入に使用する携帯電話の現在の在庫を示し、500元または200元の保証金を支払ったユーザーはこの制限の対象外です。
無責任モデル
責任連鎖パターンを使用する場合は、次のように記述できます。
var order = function( orderType, pay, stock ){
if ( orderType === 1 ){
// 500元定金购买模式
if ( pay === true ){
// 已支付定金
console.log( '500元定金预购,得到100优惠券’ );
}else{
// 未支付定金,降级到普通购买模式
if ( stock > 0 ){
// 用于普通购买的手机还有库存
console.log( ’普通购买,无优惠券’ );
}else{
console.log( '手机库存不足' );
}
}
}
else if ( orderType === 2 ){
// 200元定金购买模式
if ( pay === true ){
console.log( '200元定金预购, 得到50优惠券' );
}else{
if ( stock > 0 ){
console.log( '普通购买, 无优惠券' );
}else{
console.log( '手机库存不足' );
}
}
}
else if ( orderType === 3 ){
if ( stock > 0 ){
console.log( '普通购买, 无优惠券' );
}else{
console.log( '手机库存不足' );
}
}
};
order( 1 , true, 500); // 输出: 500元定金预购, 得到100优惠券
現在のプロジェクトは正常に実行されていますが、次のメンテナンス作業は間違いなく悪夢です。
責任連鎖パターンを使用してコードをリファクタリングする
まず、3 つの購入モードを表すノード関数を書き直す必要があります。ノードがリクエストを処理できない場合は、リクエストを渡す必要があることを示す特定の文字列「nextSuccessor」を返すことに同意します。(現在、これはブール値で処理できます)
var order500 = function( orderType, pay, stock ){
if ( orderType === 1 && pay === true ){
console.log( '500元定金预购,得到100优惠券’ );
}else{
return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};
var order200 = function( orderType, pay, stock ){
if ( orderType === 2 && pay === true ){
console.log( '200元定金预购,得到50优惠券’ );
}else{
return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};
var orderNormal = function( orderType, pay, stock ){
if ( stock > 0 ){
console.log( ’普通购买,无优惠券’ );
}else{
console.log( ’手机库存不足’ );
}
};
次に、関数を責任チェーン ノードにラップする必要があります。コンストラクター Chain を定義します。新しい Chain に渡されるパラメーターは、ラップする必要がある関数です。同時に、インスタンス属性 this.successor も持ちます。 、チェーン内の関数を表します。また、Chain のプロトタイプには 2 つの関数があり、その機能は次のとおりです。
// Chain.prototype.setNextSuccessor 指定在链中的下一个节点
// Chain.prototype.passRequest 传递请求给某个节点
var Chain = function( fn ){
this.fn = fn;
this.successor = null;
};
Chain.prototype.setNextSuccessor = function( successor ){
return this.successor = successor;
};
Chain.prototype.passRequest = function(){
var ret = this.fn.apply( this, arguments );
if ( ret === 'nextSuccessor' ){
return this.successor && this.successor.passRequest.apply( this.successor, arguments );
}
return ret;
};
ここで、3 つの順序関数を責任連鎖のノードとしてパッケージ化します。
var chainOrder500 = new Chain( order500 );
var chainOrder200 = new Chain( order200 );
var chainOrderNormal = new Chain( orderNormal );
次に、責任の連鎖におけるノードの順序を指定します。
chainOrder500.setNextSuccessor( chainOrder200 );
chainOrder200.setNextSuccessor( chainOrderNormal );
最後にリクエストを最初のノードに渡します。
chainOrder500.passRequest( 1, true, 500 ); // 输出:500元定金预购,得到100优惠券
chainOrder500.passRequest( 2, true, 500 ); // 输出:200元定金预购,得到50优惠券
chainOrder500.passRequest( 3, true, 500 ); // 输出:普通购买,无优惠券
chainOrder500.passRequest( 1, false, 0 ); // 输出:手机库存不足
改良により、チェーン内のノードの順序を自由かつ柔軟に追加、削除、変更できるようになり、ある日ウェブサイト運営者が購入サポートとして 300 元のデポジットを用意してくれた場合、チェーンにノードを追加できます。
var order300 = function(){
// 具体实现略
};
chainOrder300= new Chain( order300 );
chainOrder500.setNextSuccessor( chainOrder300);
chainOrder300.setNextSuccessor( chainOrder200);
非同期の責任の連鎖
各ノード関数が同期的に特定の値「nextSuccessor」を返し、リクエストを次のノードに渡すかどうかを示すようにします。実際の開発では、非同期の問題が頻繁に発生します。たとえば、ノード関数で ajax 非同期リクエストを開始する必要があり、非同期リクエストによって返された結果によって、責任チェーン内で passRequest を続行するかどうかが決まります。
現時点では、ノード関数が同期的に「nextSuccessor」を返すことは無意味であるため、プロトタイプ メソッド Chain.prototype.next を Chain クラスに追加して、リクエストがチェーン内の次のノードに手動で渡されることを示す必要があります。責任の所在:
Chain.prototype.next= function(){
return this.successor && this.successor.passRequest.apply( this.successor, arguments );
};
非同期の責任連鎖の例を見てみましょう。
var fn1 = new Chain(function(){
console.log( 1 );
return 'nextSuccessor';
});
var fn2 = new Chain(function(){
console.log( 2 );
var self = this;
setTimeout(function(){
self.next();
}, 1000 );
});
var fn3 = new Chain(function(){
console.log( 3 );
});
fn1.setNextSuccessor( fn2 ).setNextSuccessor( fn3 );
fn1.passRequest();
ここで、特別なチェーンを取得します。リクエストはチェーン内のノード間で渡されますが、リクエストをいつ次のノードに渡すかを決定する権利はノードにあります。非同期責任連鎖とコマンド モード (ajax リクエストをコマンド オブジェクトにカプセル化します。詳細については第 9 章を参照してください) を使用すると、非同期 ajax キュー ライブラリを簡単に作成できると考えられます。
責任連鎖パターンの長所と短所
前述したように、責任連鎖モデルの最大の利点は、リクエストの送信者と N 個の受信者の間の複雑な関係を切り離すことです。チェーン内のどのノードがリクエストを処理できるかわからないため、送信するだけで済みます。リクエストを最初のノードに渡すだけです。
アドバンテージ
責任連鎖モデルによる改善後: 携帯電話ショップの例では、条件分岐ステートメントが満載の巨大な機能を保守する必要がありましたが、この例では、購入プロセス中に出力されるログ ステートメントは 1 つだけでした。実際、実際の開発では、注文の種類に応じて異なるフローティング レイヤー プロンプトをポップアップ表示したり、異なる UI ノードをレンダリングしたり、異なるパラメーターを組み合わせて異なる CGI に送信したりするなど、ここで行うべきことはさらにあります。責任チェーンモードを使用した後、各注文は相互に影響を与えることなく独自の処理機能を持ちます。
欠点がある
Chain of Responsibility モードでは、プログラムにいくつかのノード オブジェクトが追加されます。おそらく、特定のリクエスト配信プロセスでは、ほとんどのノードは実質的な役割を果たしません。それらの役割は、リクエストを渡すことだけです。パフォーマンスの観点から、責任の連鎖が長すぎることによるパフォーマンスの低下を避けるため。
AOP による責任連鎖の実現
unction.prototype.after 関数を使用して、最初の関数が 'nextSuccessor' を返したときに、リクエストが次の関数に渡されるようにします。文字列 'nextSuccessor' を返すか false を返すかは、単なる契約です。もちろん、次のようにすることもできます。関数 Return false を使用してリクエストを渡すと、「nextSuccessor」文字列の方が目的にとってより表現力があると思われるため、「nextSuccessor」文字列が選択されました。
Function.prototype.after = function( fn ){
var self = this;
return function(){
var ret = self.apply( this, arguments );
if ( ret === 'nextSuccessor' ){
return fn.apply( this, arguments );
}
return ret;
}
};
var order = order500yuan.after( order200yuan ).after( orderNormal );
order( 1, true, 500 ); // 输出:500元定金预购,得到100优惠券
order( 2, true, 500 ); // 输出:200元定金预购,得到50优惠券
order( 1, false, 500 ); // 输出:普通购买,无优惠券
AOP を使用して責任の連鎖を実装するのはシンプルで独創的ですが、関数をスタックするこの方法では関数の範囲も重なり、連鎖が長すぎるとパフォーマンスへの影響も大きくなります。