1. 程式人生 > >設計模式(八)模板方法模式

設計模式(八)模板方法模式

老師在黑板上抄題目,我們要先抄題目,再做答案。請把抄題目的程式寫出來。

第一版(重複=易錯+難改)

程式碼結構圖:

學生甲抄的試卷類:

// 學生甲抄的試卷
class TestPaperA{
    // 試題1
    public void TestQuestion1(){
        Console.WriteLine("123+456=[] a.123 b.456 c 579 d.123456");
        Console.WriteLine("答案:a");
    }

    // 試題2
    public void TestQuestion2(){
        Console.WriteLine("1+1=[] a.1 b.2 c 3 d.11");
        Console.WriteLine("答案:b");
    }    
}

學生乙抄的試卷類:

// 學生乙抄的試卷
class TestPaperB{
    // 試題1
    public void TestQuestion1(){
        Console.WriteLine("123+456=[] a.123 b.456 c 579 d.123456");
        Console.WriteLine("答案:c");
    }

    // 試題2
    public void TestQuestion2(){
        Console.WriteLine("1+1=[] a.1 b.2 c 3 d.11");
        Console.WriteLine("答案:b");
    }    
}

客戶端程式碼:

static void Main(string[] args){
    Console.WriteLine("學生甲抄的試卷:");
    TestPaperA studentA = new TestPaperA();
    studentA.TestQuestion1();
    studentA.TestQuestion2();
    studentA.TestQuestion3();

    Console.WriteLine("學生乙抄的試卷:");
    TestPaperB studentB = new TestPaperB();
    studentB.TestQuestion1();
    studentB.TestQuestion2();
    studentB.TestQuestion3();

    Console.Read();
}

有發現問題嗎?

學生甲和學生乙兩個抄試卷類非常類似,除了答案不同,沒什麼不一樣。這樣寫又容易錯,又難以維護。如果老師突然要改題目,或者某人抄錯了,那就非常糟糕了。

其實,應該讓老師出一份試卷,列印多份,讓學生填寫答案就可以了。在這裡應該就是把試題和答案分享,抽象出一個父類,讓兩個子類繼承於它,公共的試題程式碼寫到父類當中,就可以了。

第二版

試卷父類程式碼:

// 金庸小說考題程式碼
class TestPaper{
    public void TestQuestion1(){
         Console.WriteLine("123+456=[] a.123 b.456 c 579 d.123456");
    }

    public void TestQuestion2(){
        Console.WriteLine("1+1=[] a.1 b.2 c 3 d.11");
    }    
}

學生子類程式碼:

// 學生甲抄的試卷
class TestPaperA:TestPaper{
    // 試題1
    public new void TestQuestion1(){
        base.TestQuestion1();
        Console.WriteLine("答案:a");
    }

    // 試題2
    public new void TestQuestion2(){
        base.TestQuestion2();
        Console.WriteLine("答案:b");
    }    
}

學生乙類似。

客戶端程式碼完全相同,略。

這下子類就非常簡單了,只需要填寫答案就可以。然而,這還只是初步的泛化,子類中仍有重複的程式碼,如“base.TestQuestion1();”、“ Console.WriteLine("答案:");”,——似乎除了選項的abcd,其他都是重複的。

我們既然用了繼承,並且肯定這個繼承有意義,就應該要成為子類的模版。所有重複的程式碼都應該要上升到父類去,而不是讓每個子類都去重複。

模板方法登場了——

當我們要完成在某一細節層次一致的一個過程或一系列步驟,但其個別步驟在更詳細的層次上的實現可能不同時,我們通常考慮用模板方法模式來處理。

剛才已經分析,在初始各學生的試卷中,只有答案選項可能不同,其他全部是一樣的。

於是我們就改動這裡,增加一個虛方法:

public void TestQuestion1(){
     Console.WriteLine("123+456=[] a.123 b.456 c 579 d.123456");
     Console.WriteLine("答案:" + Answer1());     // 改成一個虛方法
}

// 此方法的目的就是給繼承的子類重寫,
// 因為這裡每個人的答案都是不同的
protected virtual string Answer1(){
    return "";
}

其餘兩個題目也用相同的做法。

然後子類就非常簡單了,重寫虛方法後,把答案填上,其他什麼都不用管,因為父類建立了所有重複的模版。

// 學生甲抄的試卷
class TestPaperA : TestPaper{
    protected override string Answer1(){
        return "a";
    }
    
    protected override string Answer2(){
        return "b";
    }
}

// 學生乙類似

程式碼結構圖:

客戶端程式碼需要改動一個小地方,即原來是子類變數的宣告,改成了父類。這樣就可利用多型性實現程式碼的複用了。

static void Main(string[] args){
    Console.WriteLine("學生甲抄的試卷:");
    // 將子類變數的宣告改成了父類,利用了多型性,實現了程式碼的複用
    TestPaper studentA = new TestPaperA();
    studentA.TestQuestion1();
    studentA.TestQuestion2();
    studentA.TestQuestion3();

    Console.WriteLine("學生乙抄的試卷:");
    TestPaper studentB = new TestPaperB();
    studentB.TestQuestion1();
    studentB.TestQuestion2();
    studentB.TestQuestion3();

    Console.Read();
}

此時如有更多的學生來答試卷,只不過是在試卷的模版上填寫選擇題的選項答案,這是每個人試卷的唯一不同。(還有姓名)

模板方法模式

模板方法模式定義一個操作中的演算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟

AbstractClass是抽象類,其實也就是一抽象模板,定義並實現了一個模版方法。這個模版方法一般是一個具體方法,它給出了一個頂級邏輯的骨架,而邏輯的組成步驟在相應的抽象操作中,推遲到子類實現。頂級邏輯也有可能呼叫一些具體方法。

abstract class AbstractClass{
    // 一些抽象行為,放到子類去實現
    public abstract void PrimitiveOperation1();
    public abstract void PrimitiveOperation2();
    
    // 模版方法,給出了邏輯的骨架,而邏輯的組成是一些相應的抽象操作。
    // 它們都推遲到子類實現
    public void TemplateMethod(){
        PrimitiveOperation1();
        PrimitiveOperation2();
        Console.WriteLine("");
    }
}

ConcreteClass,實現父類所定義的一個或多個抽象方法。每一個AbstractClass都可以有任意多個ConcreteClass與之對應,而每一個ConcreteClass都可以給出這些抽象方法(也就是頂級邏輯的組成步驟)的不同實現,從而使得頂級邏輯的實現各不相同

class ConcreteClassA : AbstractClass{
    // 與ConcreteClassB不同的方法實現
    public override void PrimitiveOperation1(){
        Console.WriteLine("具體類A方法1實現");
    }
    public override void PrimitiveOperation2(){
        Console.WriteLine("具體類A方法2實現");
    }    
}

class ConcreteClassB : AbstractClass{
    // 與ConcreteClassA不同的方法實現
    public override void PrimitiveOperation1(){
        Console.WriteLine("具體類B方法1實現");
    }
    public override void PrimitiveOperation2(){
        Console.WriteLine("具體類A方法2實現");
    }    
}

客戶端呼叫:

static void Main(string[] args){
    AbstractClass c;

    c = new ConcreteClassA();
    c.TemplateMethod();

    c = new ConcreteClassB();
    c.TemplateMethod();

    Console.Read();
}

總結

模板方法模式是通過把不變行為搬移到超類,去除子類中的重複程式碼來體現它的優勢。其實,模板方法模式就是提供了一個很好的程式碼複用平臺

本章完。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 本文是連載文章,此為第八章,學習定義一個操作中的演算法的骨架,而把具體實現細節留給子類的模板模式。

下一章:

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------