javascript設計模式 之 8 模板方法模式

zhaoyezi發表於2018-06-01

1 模板方法模式定義

模板方法模式:只需要使用整合就能實現。由兩部分組成:抽象父類 + 具體的實現子類。

  • 抽象父類:封裝子類的演算法框架,包括實現一些公用方法以及封裝在子類中所有方法的執行順序
  • 實現子類:通過整合這個抽象類,也繼承了整個演算法,並且可以選擇重寫父類的方法 假如我們有許多平行的類,各個類之間有許多相同的行為,也有部分不同的行為。如果各位都定義自己所有的行為,那麼會出現很多重複的方法。此時可以將相同的行為搬移到另外一個單一的地方,模板方法模式就是為了解決這個問題。在模板方法模式中,子類中相同的行為被移動到了父類中,而將不同的部分留待子類來實現。

2 咖啡與茶(模板方法模式案例)

我們現在需要泡一杯茶和咖啡,思考泡茶與咖啡的過程。

javascript設計模式 之 8 模板方法模式

不管泡茶還是咖啡都會有四個步驟,總結出來如下。我們抽象一個父類表示泡一杯飲料的過程。

  • 煮沸水,相同點
  • 沸水+原料(不同點,咖啡,茶葉)
  • 將飲料倒入杯子,相同點
  • 加調料(不同點:糖與牛奶,檸檬)

抽象父類

由於javascript沒有型別檢查,我們需要讓子類必須實現brew, pourInup和addCondiments,因此這裡通過丟擲異常來提醒編寫者。

var Beverage = function() { };
Beverage.prototype.boilWater = function() {
    console.log('煮沸水');
};
Beverage.prototype.brew = function(){
    throw new Error( '子類必須重寫 brew 方法' );
}; // 空方法,應該由子類重寫
Beverage.prototype.pourInCup = function(){
    throw new Error( '子類必須重寫 pourInCup 方法' );
}; // 空方法,應該由子類重寫
Beverage.prototype.addCondiments = function(){
    throw new Error( '子類必須重寫 addCondiments 方法' );
}; // 空方法,應該由子類重寫
Beverage.prototype.init = function() {
    this.boilWater();
    this.brew();
    this.pourInCup();
    this.addCondiments();
}
複製程式碼

建立Coffee子類

接下來要重寫抽象父類中的一些方法。只要把水煮沸這個行為可以直接使用父類。

var Coffee = function() { };
Coffee.prototype = new Beverage();
Coffee.prototype.brew = function(){
    console.log( '用沸水沖泡咖啡' );
};
Coffee.prototype.pourInCup = function(){
    console.log( '把咖啡倒進杯子' );
};
Coffee.prototype.addCondiments = function(){
    console.log( '加糖和牛奶' );
}; 

// 當呼叫init方法時,會找到父類的init方法進行呼叫。
var coffee = new Coffee();
coffee.init();
複製程式碼

建立Tea子類

var Tea = function() { };
Tea.prototype = new Beverage();
Tea.prototype.brew = function(){
    console.log( '用沸水浸泡茶葉' );
};
Tea.prototype.pourInCup = function(){
    console.log( '把茶倒進杯子' );
};
Tea.prototype.addCondiments = function(){
    console.log( '加檸檬' );
}; 

var tea = new Tea();
tea.init();
複製程式碼

模板方法

上面的例子,tea和Coffee都繼承了Beverage,那麼誰是模板方法呢?Beverage.prototype.init就是模板方法。因為它內部封裝了子類的演算法框架,它作為一個演算法的模板,知道子類以何種順序執行哪些方法。

3 鉤子方法

平時遇到正常的,喝咖啡的都是上面的順序,但是如果有些人不喜歡加調料,那麼上面的步驟又不符合情況了,此時可以通過鉤子函式來進行解決。鉤子函式通過使用者返回的結果來決定接下來的步驟。究竟要不要鉤子由子類自己決定。

var Beverage = function() { };
Beverage.prototype.boilWater = function() {
    console.log('煮沸水');
};
Beverage.prototype.brew = function(){
    throw new Error( '子類必須重寫 brew 方法' );
}; 
....

// 鉤子函式
Beverage.prototype.customerWantsCondiments = function(){
    return true;
}; 

Beverage.prototype.init = function() {
    this.boilWater();
    this.brew();
    this.pourInCup();
    // 如果鉤子函式返回true,則新增調料
    if (this,customerWantsCondiments()) {
        this.addCondiments();
    }    
}
複製程式碼

在子類Coffee中,需要實現鉤子函式

var Coffee = function() { };
Coffee.prototype = new Beverage();
Coffee.prototype.brew = function(){
    console.log( '用沸水沖泡咖啡' );
};
...
// 重寫鉤子函式
Coffee.prototype.customerWantsCondiments = function(){
    return window.confirm('請問需要調料嗎?');
};
// 當呼叫init方法時,會找到父類的init方法進行呼叫。
var coffee = new Coffee();
coffee.init();
複製程式碼

4 真的需要繼承嗎

模板方法模式就是基於繼承的一種設計模式,父類中封裝了子類的演算法框架和執行順序,子類繼承父類後,父類通知子類執行這些方法。但是javascript並沒有提供真正的類式繼承,繼承是通過物件與物件之間的委託來實現的,也就是形式上借鑑了提供類式的語言。下面這段程式碼能夠達到一樣的繼承效果。

var Beverage = function( param ){
    var boilWater = function(){
        console.log( '把水煮沸' );
    };
    var brew = param.brew || function(){
        throw new Error( '必須傳遞 brew 方法' );
    };
    var pourInCup = param.pourInCup || function(){
        throw new Error( '必須傳遞 pourInCup 方法' );
    };
    var addCondiments = param.addCondiments || function(){
        throw new Error( '必須傳遞 addCondiments 方法' );
    };
    var F = function(){};
    F.prototype.init = function(){
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    };
    return F;
};
var Coffee = Beverage({
    brew: function(){
    console.log( '用沸水沖泡咖啡' );
    },
    pourInCup: function(){
    console.log( '把咖啡倒進杯子' );
    },
    addCondiments: function(){
    console.log( '加糖和牛奶' );
    }
});
複製程式碼

5 小結

模板方法模式在傳統的程式語言中,子類的方法種類以及執行順序都是不變的,這部分邏輯我們都抽象到了父類中,而子類的方法具體怎麼實現是可變的,通過重寫父類的方法,將變化的邏輯部分封裝到子類中。通過增加新的子類,我們能夠給系統增加新的功能的,俺是並不需要修改父類以及其他的子類,這也符合開放-封閉原則。在javascript中,我們不需要依樣畫瓢去實現一個模板方法模式,因為高階函式是一個更好的選擇。

相關文章