js之模板方法模式

模板方法模式的定義和組成:

模板方法模式是一種只需使用繼承就能夠實現的很是簡單的模式。 
模板方法模式由兩部分結構組成,第一部分是抽象父類,第二部分是具體的實現子類。一般在抽象父類中封裝了子類的算法框架,包括實現一些公共方法以及封裝子類中全部方法的執行順 
序。子類經過繼承這個抽象類,也繼承了整個算法結構,而且能夠選擇重寫父類的方法。javascript

假如咱們有一些平行的子類,各個子類之間有一些相同的行爲,也有一些不一樣的行爲。若是相同和不一樣的行爲都混合在各個子類的實現中,說明這些相同的行爲會在各個子類中重複出現。但實際上,相同的行爲能夠被搬移到另一個單一的地方,模板方法模式就是爲解決這個問題而生的。在模板方法模式中,子類實現中的相同部分被上移到父類中,而將不一樣的部分留待子類來實現。這也很好地體現了泛化的思想。html

先泡一杯咖啡:

首先,咱們先來泡一杯咖啡,若是沒有什麼太個性化的需求,泡咖啡的步驟一般以下: 
(1) 把水煮沸 
(2) 用沸水沖泡咖啡 
(3) 把咖啡倒進杯子 
(4) 加糖和牛奶 
經過下面這段代碼,咱們就能獲得一杯香濃的咖啡:java

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script>
        var Coffee = function(){};
        Coffee.prototype.boilWater = function(){
            console.log( '把水煮沸' );
        };
        Coffee.prototype.brewCoffeeGriends = function(){
            console.log( '用沸水沖泡咖啡' );
        };
        Coffee.prototype.pourInCup = function(){
            console.log( '把咖啡倒進杯子' );
        };
        Coffee.prototype.addSugarAndMilk = function(){
            console.log( '加糖和牛奶' );
        };
        Coffee.prototype.init = function(){
            this.boilWater();
            this.brewCoffeeGriends();
            this.pourInCup();
            this.addSugarAndMilk();
        };
        var coffee = new Coffee();
        coffee.init();
    </script>
</body>
</html>
 

泡一壺茶:

接下來,開始準備咱們的茶,泡茶的步驟跟泡咖啡的步驟相差並不大: 
(1) 把水煮沸 
(2) 用沸水浸泡茶葉 
(3) 把茶水倒進杯子 
(4) 加檸檬 
一樣用一段代碼來實現泡茶的步驟:ajax

var Tea = function(){};
Tea.prototype.boilWater = function(){
    console.log( '把水煮沸' );
};
Tea.prototype.steepTeaBag = function(){
    console.log( '用沸水浸泡茶葉' );
};
Tea.prototype.pourInCup = function(){
    console.log( '把茶水倒進杯子' );
};
Tea.prototype.addLemon = function(){
    console.log( '加檸檬' );
};
Tea.prototype.init = function(){
    this.boilWater();
    this.steepTeaBag();
    this.pourInCup();
    this.addLemon();
};
var tea = new Tea();
tea.init();

 

 

分離出共同點:

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 子類和 Tea 子類:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script>
        var Beverage = function(){};
        Beverage.prototype.boilWater = function(){
            console.log( '把水煮沸' );
        };
        Beverage.prototype.brew = function(){}; // 空方法,應該由子類重寫
        Beverage.prototype.pourInCup = function(){}; // 空方法,應該由子類重寫
        Beverage.prototype.addCondiments = function(){}; // 空方法,應該由子類重寫
        Beverage.prototype.init = function(){
            this.boilWater();
            this.brew();
            this.pourInCup();
            this.addCondiments();
        };

        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( '加糖和牛奶' );
        };
        var Coffee = new Coffee();
        Coffee.init();
    </script>
</body>
</html>

 

本章一直討論的是模板方法模式,那麼在上面的例子中,到底誰纔是所謂的模板方法呢?答案是 Beverage.prototype.init 。算法

Beverage.prototype.init 被稱爲模板方法的緣由是,該方法中封裝了子類的算法框架,它做爲一個算法的模板,指導子類以何種順序去執行哪些方法。在 Beverage.prototype.init 方法中, 
算法內的每個步驟都清楚地展現在咱們眼前。框架

模板方法模式的使用場景:

在 Web開發中也能找到不少模板方法模式的適用場景,好比咱們在構建一系列的 UI組件, 
這些組件的構建過程通常以下所示: 
(1) 初始化一個 div容器; 
(2) 經過 ajax請求拉取相應的數據; 
(3) 把數據渲染到 div容器裏面,完成組件的構造; 
(4) 通知用戶組件渲染完畢。this

咱們看到,任何組件的構建都遵循上面的 4 步,其中第(1)步和第(4)步是相同的。第(2)步不一樣的地方只是請求 ajax的遠程地址,第(3)步不一樣的地方是渲染數據的方式。spa

因而咱們能夠把這 4個步驟都抽象到父類的模板方法裏面,父類中還能夠順便提供第(1)步和第(4)步的具體實現。當子類繼承這個父類以後,會重寫模板方法裏面的第(2)步和第(3)步。prototype

添加鉤子

經過模板方法模式,咱們在父類中封裝了子類的算法框架。這些算法框架在正常狀態下是適用於大多數子類的,但若是有一些特別「個性」的子類呢?好比咱們在飲料類 Beverage 中封裝了 
飲料的沖泡順序: 
(1) 把水煮沸 
(2) 用沸水沖泡飲料 
(3) 把飲料倒進杯子 
(4) 加調料 
這 4個沖泡飲料的步驟適用於咖啡和茶,在咱們的飲料店裏,根據這 4個步驟製做出來的咖啡和茶,一直順利地提供給絕大部分客人享用。但有一些客人喝咖啡是不加調料(糖和牛奶)的。既然 Beverage 做爲父類,已經規定好了沖泡飲料的 4個步驟,那麼有什麼辦法可讓子類不受這個約束呢?code

鉤子方法( hook )能夠用來解決這個問題,放置鉤子是隔離變化的一種常見手段。咱們在父類中容易變化的地方放置鉤子,鉤子能夠有一個默認的實現,究竟要不要「掛鉤」,這由子類自行決定。鉤子方法的返回結果決定了模板方法後面部分的執行步驟,也就是程序接下來的走向,這樣一來,程序就擁有了變化的可能。在這個例子裏,咱們把掛鉤的名字定爲customerWantsCondiments ,接下來將掛鉤放入 Beverage 
類,看看咱們如何獲得一杯不須要糖和牛奶的咖啡,代碼以下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script>
        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.customerWantsCondiments = function(){
            return true; // 默認須要調料
        };
        Beverage.prototype.init = function(){
            this.boilWater();
            this.brew();
            this.pourInCup();
            if ( this.customerWantsCondiments() ){ // 若是掛鉤返回 true,則須要調料
                this.addCondiments();
            }
        };
        var CoffeeWithHook = function(){};
        CoffeeWithHook.prototype = new Beverage();
        CoffeeWithHook.prototype.brew = function(){
            console.log( '用沸水沖泡咖啡' );
        };
        CoffeeWithHook.prototype.pourInCup = function(){
            console.log( '把咖啡倒進杯子' );
        };
        CoffeeWithHook.prototype.addCondiments = function(){
            console.log( '加糖和牛奶' );
        };
        CoffeeWithHook.prototype.customerWantsCondiments = function(){
            return window.confirm( '請問須要調料嗎?' );
        };
        var coffeeWithHook = new CoffeeWithHook();
        coffeeWithHook.init();
    </script>
</body>
</html>
 

真的須要「繼承」嗎?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script>
        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( '加糖和牛奶' );
            }
        });
        var Tea = Beverage({
            brew: function(){
                console.log( '用沸水浸泡茶葉' );
            },
            pourInCup: function(){
                console.log( '把茶倒進杯子' );
            },
            addCondiments: function(){
                console.log( '加檸檬' );
            }
        });
        var coffee = new Coffee();
        coffee.init();
        var tea = new Tea();
        tea.init();
    </script>
</body>
</html>
相關文章
相關標籤/搜索