1. 程式人生 > >設計模式學習總結:模板方法模式(Template Method)

設計模式學習總結:模板方法模式(Template Method)

1.場景分析

茶和咖啡是兩種飲料,但是它們的沖泡方法十分相似,沖泡方法如下。
茶:把水煮沸->沸水浸泡茶葉->把茶倒進杯子->加檸檬
咖啡:把水煮沸->用沸水沖泡咖啡->把咖啡裝進杯子->加糖和牛奶
用程式碼來實現咖啡和茶,如下:

class Coffee
{
public:
    //準備一杯咖啡
    void prepareRecipe()
    {
        boilWater();
        brewCofferGrainds();
        pourInCup();
        addSugarAndMilk()
; } void boilWater(); void brewCofferGrainds(); void pourInCup(); void addSugarAndMilk(); }; class Tea { public: //準備一杯茶 void prepareRecipe() { boilWater(); steepTeaBag(); pourInCup(); addLemon(); } void boilWater(); void steepTeaBag()
; void pourInCup(); void addLemon(); };

現在我們從咖啡和茶中抽象出一個基類:咖啡因飲料(CaffeineBeverage)。我們將咖啡的brewCoffeeGrinds()和茶的steepTeaBag()函式抽象為一個函式brew(),將addSugarAndMilk()和addLemon()抽象為addCondiments(),寫出基類如下:

#include <iostream>
using namespace std;

class CaffieineBeverage
{
public:
    void prepareRecipe
() { boilWater(); brew(); pourInCup(); addCondiments(); } void boilWater(); virtual void brew() = 0; void pourInCup(); virtual void addCondiments() = 0; }; class Coffee : public CaffieineBeverage { public: void brew() override { cout << "brew coffee grinds" << endl; } void addCondiments() override { cout << "add milk and sugar" << endl; } }; class Tea : public CaffieineBeverage { public: void brew() override { cout << "steep tea bag" << endl; } void addCondiments() override { cout << "add lemon" << endl; } };

上面我們實現的CaffieineBeverage類便實現了模板方法。模板方法定義了一個演算法的步驟,並允許子類為一個或多個步驟提供實現。比如我們提供了prepareRecipe()的模板步驟,而其中有兩個步驟brew()、addCondiments()為虛擬函式,其可以供其子類自定義。

2.意圖

模板方法模式在一個方法中定義一個演算法的骨架,而演算法中的一些步驟則延遲到子類中。模板方法使得子類可以在不改變演算法結構的情況下,重新定義演算法中的某些步驟。

3.實用性

  1. 一次性實現一個演算法的不變的部分,並將可變的行為留給子類來實現。
  2. 各子類中的公共行為被提取出來並集中到一個公共父類中以避免程式碼重複。
  3. 可以通過鉤子(hook)控制子類擴充套件。

4.結構

這裡寫圖片描述

5.效果

模板方法是一種程式碼複用的基本技術。它們在類庫中尤為重要,他們提取了類庫中的公共方法。模板方法導致了一種反向的控制結構,即父類呼叫一個子類的操作,而不是相反。
模板方法中經常用到鉤子(hook)操作。鉤子操作預設通常是一個空操作,其可以在子類中進行擴充套件。模板方法應該指明哪些操作是鉤子操作(可以被重定義)以及哪些操作是抽象操作(必須被重定義)。

6.實現

有三個實現問題值得注意:
1>使用c++訪問控制。一個模板方法呼叫的原語操作可以定義為保護成員,這保證它們只能被模板方法呼叫。
2>儘量減少原語操作。子類需要重定義的操作越多,客戶程式就越冗長。
3>命名約定。可以給應被重定義的那些操作的名字加上一個字首以識別它們。

7.程式碼示例

class View
{
public:
    void display()
    {
        setFocus();
        DoDisplay();
        ResetFocus();
    }

protected:
    void setFocus();
    void ResetFocus();
    virtual void DoDisplay() {}
};

class MyView : public View
{
protected:
    void DoDisplay() override
    {
        cout << "render the view's contents" << endl;
    }
};

繪圖類View,其只在獲得焦點時才進行繪圖。其定義了一個顯式的模板方法,並提供了一個鉤子DoDisplay()來讓子類擴充套件繪圖的內容與樣式。