職責(zé)鏈模式的定義是: 使多個(gè)對(duì)象都有機(jī)會(huì)處理請(qǐng)求炮温,從而避免請(qǐng)求的發(fā)送者和接收者之間的耦合關(guān)系,將這些對(duì)象形成一條鏈牵舵,并沿著這條鏈傳遞該請(qǐng)求柒啤,直到有一個(gè)對(duì)象處理它為止。
舉一個(gè)實(shí)際的例子:一款新手機(jī)發(fā)布會(huì)后畸颅,經(jīng)過(guò)了繳納過(guò)500元定金和200元定金的兩輪預(yù)售后担巩,現(xiàn)在到了正式購(gòu)買的階段。 廠家對(duì)交付過(guò)定金的用戶有一定的優(yōu)惠政策:繳納500的用戶没炒,會(huì)得到一張100元的優(yōu)惠券涛癌;繳納200的用戶,會(huì)得到一張30元的優(yōu)惠券送火;沒有交付定金的用戶拳话,沒有優(yōu)惠券,只能進(jìn)入普通購(gòu)買模式种吸,而且在庫(kù)存有限的情況下弃衍,不一定能買得到。
具體的程序?qū)崿F(xiàn)如下:
var order = function(orderType, pay, stock){
if(orderType == 1){ // 500元定金模式
if(pay){
console.log("500元定金坚俗, 得到100優(yōu)惠券");
}else{
if(stock > 0){ // 還有庫(kù)存
console.log("普通購(gòu)買 無(wú)優(yōu)惠券");
}else{
console.log("手機(jī)庫(kù)存不足");
}
}
}else if(orderType == 2){ // 100元定金模式
if(pay){
console.log("200元定金镜盯, 得到30優(yōu)惠券");
}else{
if(stock > 0){ // 還有庫(kù)存
console.log("普通購(gòu)買 無(wú)優(yōu)惠券");
}else{
console.log("手機(jī)庫(kù)存不足");
}
}
}else if(orderType == 3){ // 無(wú)定金模式
if(stock > 0){ // 還有庫(kù)存
console.log("普通購(gòu)買 無(wú)優(yōu)惠券");
}else{
console.log("手機(jī)庫(kù)存不足");
}
}
}
上面的程序難以閱讀而且如要進(jìn)行修改,工作量會(huì)很大坦冠。切合本章的主題形耗,我們引入職責(zé)鏈模式:
// 500定金
var order500 = function(orderType, pay, stock){
if(orderType == 1 && pay){
console.log("500元定金, 得到100優(yōu)惠券");
}else{
order200(orderType, pay, stock); // 此處order500 和 order200的耦合
}
}
// 200定金
var order200 = function(orderType, pay, stock){
if(orderType == 2 && pay){
console.log("200元定金辙浑, 得到30優(yōu)惠券");
}else{
orderNromal(orderType, pay, stock);// 此處orderNromal 和 order200的耦合
}
}
// 普通
var orderNromal = function(orderType, pay, stock){
if(stock > 0){ // 還有庫(kù)存
console.log("普通購(gòu)買 無(wú)優(yōu)惠券");
}else{
console.log("手機(jī)庫(kù)存不足");
}
}
通過(guò)這種形式個(gè)改造激涤,整個(gè)程序結(jié)構(gòu)就清晰很多。但是其中還是有耦合的問(wèn)題判呕,例如如果加一個(gè)300定金的情況倦踢,那么order500 order200都需要進(jìn)行相應(yīng)邏輯的修改。為此我們進(jìn)一步進(jìn)行程序的優(yōu)化:
// 500定金
var order500 = function(orderType, pay, stock){
if(orderType == 1 && pay){
console.log("500元定金侠草, 得到100優(yōu)惠券");
}else{
return 'nextSuccessor';
}
}
// 200定金
var order200 = function(orderType, pay, stock){
if(orderType == 2 && pay){
console.log("200元定金辱挥, 得到30優(yōu)惠券");
}else{
return 'nextSuccessor';
}
}
// 普通
var orderNromal = function(orderType, pay, stock){
if(stock > 0){ // 還有庫(kù)存
console.log("普通購(gòu)買 無(wú)優(yōu)惠券");
}else{
console.log("手機(jī)庫(kù)存不足");
}
}
// 構(gòu)造函數(shù)chain
var Chain = function( fn ){
this.fn = fn;
this.successor = null;
}
// 設(shè)置下一個(gè)執(zhí)行者
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;
}
// 把三個(gè)包裝城職責(zé)鏈的節(jié)點(diǎn)
var chainOrder500 = new Chain(order500);
var chainOrder200 = new Chain(order200);
var chainOrderNromal = new Chain(orderNromal);
// 制定職責(zé)鏈中順序
chainOrder500.setNextSuccessor(chainOrder200);
chainOrder200.setNextSuccessor(chainOrderNromal);
// 如下是執(zhí)行
chainOrder500.passRequest(1, true, 500); // 500元定金, 得到100優(yōu)惠券
chainOrder500.passRequest(2, true, 500); // 200元定金边涕, 得到30優(yōu)惠券
// 借助AOP晤碘,可以把上面的Chain改成更為方便的形式
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 = order500.after(order200).after(orderNormal);
職責(zé)鏈模式一方面更加清晰的組織我們的代碼褂微;還可以靈活地拆分重組節(jié)點(diǎn)對(duì)象;另外可以手動(dòng)設(shè)定起始節(jié)點(diǎn)园爷,因?yàn)檎?qǐng)求并不一定從最前面一個(gè)節(jié)點(diǎn)開始(比如一個(gè)擠滿人的公交車宠蚂,你從后門上車,把錢傳遞給售票員童社,你的終點(diǎn)是固定的求厕,但是你并不能確定誰(shuí)是第一個(gè)接到你錢的人)。 唯一的問(wèn)題是扰楼,要保證鏈不能太長(zhǎng)呀癣,要不會(huì)引發(fā)性能問(wèn)題。
采用aop實(shí)現(xiàn)職責(zé)鏈?zhǔn)且粋€(gè)非常好的形式~
異步職責(zé)鏈
如果是異步的話弦赖,那么直接返回"nextSuccessor"就沒什么意義了项栏,所以需要給Chain類再增加一個(gè)原型方法:Chain.prototype.next
,表示手動(dòng)傳遞請(qǐng)求給職責(zé)鏈中的下一個(gè)節(jié)點(diǎn):
var Chain = function( fn ){
this.fn = fn;
this.successor = null;
}
Chain.prototype.setNextSuccessor = function( successor ){
return this.successor = successor;
}
Chain.prototype.next = function(){
return this.successor && this.successor.passRequest.apply(this.successor, arguments);
}
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;
}
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();