Home [閱讀筆記][Design Pattern] Ch10.範本方法模式(Template)
Post
Cancel

[閱讀筆記][Design Pattern] Ch10.範本方法模式(Template)

範本方法模式(Template)

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

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

範本模式結構

  • AbstractClass抽象類別:實現了一個範本方法,定義了演算法的骨架,具體子類別將重定義 PrimitiveOperaion以實現一個演算法的步驟
  • ConcreteClass具體類別:實現PrimitiveOperaion以完成演算法中與特定子類別相關的步驟
1
2
3
4
5
6
7
8
9
10
實現了一個範本方法,定義了演算法的骨架,具體子類別將重定義 PrimitiveOperaion以實現一個演算法的步驟
AbstractClass
+ TemplateMethod()
+ PrimitiveOperaion1()
+ PrimitiveOperaion2()

實現PrimitiveOperaion以完成演算法中與特定子類別相關的步驟
ConcreteClass
+ PrimitiveOperaion1()
+ PrimitiveOperaion2()

AbstractClass抽象類別

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

1
2
3
4
5
6
7
8
9
10
11
12
abstract class AbstractClass {
    //一些抽象行為,放到子類別去實現
    public abstract void PrimitiveOperaion1();
    public abstract void PrimitiveOperaion2();

    //範本方法,給出了邏輯的骨架,而邏輯的組成是一些相應的抽象操作,他們都推連到子類別實現
    public void TemplateMethod() {
        PrimitiveOperaion1();
        PrimitiveOperaion2();
        Console.WriteLine("");
    }
}

ConcreteClass具體類別

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//具體類別A
class ConcreteClassA: AbstractClass {
    //與ConcreteClassB 不同的實現方法
    public override void PrimitiveOperaion1() {
        Console.WriteLine("具體類別A-方法1實現");
    }
    public override void PrimitiveOperaion2() {
        Console.WriteLine("具體類別A-方法2實現");
    }
}
//具體類別B
class ConcreteClassB: AbstractClass {
    //與ConcreteClassA 不同的實現方法
    public override void PrimitiveOperaion1() {
        Console.WriteLine("具體類別B-方法1實現");
    }
    public override void PrimitiveOperaion2() {
        Console.WriteLine("具體類別B-方法2實現");
    }
}

用戶端調用

以父類別的類型,宣告子類變數,利用了多型,實現了程式碼的複用。

1
2
3
4
5
6
7
//以父類別的類型,宣告子類變數,利用了多型,實現了程式碼的複用
AbstractClass c;
c = new ConcreteClassA();
c.TemplateMethod();

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

範本方法模式特點

  • 「範本方法模式」是透過把不變的行為搬移到超類別(Superclass),去除子類別中的重複程式碼來表現它的優勢。

「超類別」Superclass 就是在類別階層中屬於較高層次的類別。
有人將它翻譯為「基礎類別」(base class),有人翻譯為「父類別」(parent class),

  • 「範本方法模式」就是提供了一個很好的程式碼複用平台。因為有時候,我們會遇到由一系列步驟構成的過程需要執行。這個過程從高層次上看是相同的,但有些步驟的實現可能不同。這時候,我們通常就應該要考慮用「範本方法模式」了。

  • 也就是說,當不變的和可變的行為在方法的子類別實現中混合在一起的時候,不變的行為就會在子類別重複出現。我們透過「範本方法模式」把這些行為搬移到單一的地方,這樣就幫助子類別擺脫重複的不變行為的糾纏。

重複=易錯+難改 -抄題目程式v1.0

寫一個抄題目程式,學生A和學生B類別都寫一樣的題目,這樣寫容易錯,又難以維護。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class StudentA {
    public void TestQuesition1() {
        Console.WriteLine("題目1");
        Console.WriteLine("答案:b");
    }
    //略...
}
class StudentB {
    public void TestQuesition1() {
        Console.WriteLine("題目1");
        Console.WriteLine("答案:c");
    }
    //略...
}

初步泛化-提煉程式碼 -抄題目程式v2.0

學生A和學生B抄一樣的題目,只有答案不同,可以抽象出一個父類別,讓兩個子類別繼承於它。這樣寫類別就非常簡單,只要填寫答案就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
//父類別-考試題目
class TestPaper {
    public void TestQuesition1() {
        Console.WriteLine("題目1");
    }
    public void TestQuesition2() {
        Console.WriteLine("題目2");
    }
    public void TestQuesition3() {
        Console.WriteLine("題目3");
    }
}

//子類別-同學A
class StudentA:TestPaper {
    public new void TestQuesition1() {
        base.TestQuesition1();
        Console.WriteLine("答案: b");
    }
    public new void TestQuesition2() {
        base.TestQuesition2();
        Console.WriteLine("答案: a");
    }
    public new void TestQuesition3() {
        base.TestQuesition3();
        Console.WriteLine("答案: c");
    }
}
//子類別-同學B
class StudentB:TestPaper {
    public new void TestQuesition1() {
        base.TestQuesition1();
        Console.WriteLine("答案: c");
    }
    public new void TestQuesition2() {
        base.TestQuesition2();
        Console.WriteLine("答案: a");
    }
    public new void TestQuesition3() {
        base.TestQuesition3();
        Console.WriteLine("答案: b");
    }
}

//用戶端程式碼
public static void Main()
{
    //學生A抄的試卷
    Console.WriteLine("學生A抄的試卷");
    StudentA studentA = new StudentA();
    studentA.TestQuesition1();
    studentA.TestQuesition2();
    studentA.TestQuesition3();

    //學生B抄的試卷
    Console.WriteLine("學生B抄的試卷");
    StudentB studentB = new StudentB();
    studentB.TestQuesition1();
    studentB.TestQuesition2();
    studentB.TestQuesition3();
}

/* 
學生A抄的試卷
題目1
答案: b
題目2
答案: a
題目3
答案: c
學生B抄的試卷
題目1
答案: c
題目2
答案: a
題目3
答案: b
*/

範本方法模式 -抄題目程式v3.0

但相同的東西還是有,比如都有:base.TestQuesition1();還有Console.WriteLine("答案:");,除了選項abcd,其他都是重複的。

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

那該怎麼做?使用「範本方法」。

研究最初的試題方法

只有選項abcd不一樣,其他都一樣,把選項改成一個虛擬方法。

1
2
3
4
5
public void TestQuesition1() {
    base.TestQuesition1();
    //只有選項abcd不一樣,其他都一樣,把選項改成一個虛擬方法
    Console.WriteLine("答案: a");
}

增加一個虛擬方法

增加一個答案的虛擬方法,目的是讓繼承的子類別重寫方法,因為每個人的答案都不一樣。

1
2
3
4
5
6
7
8
9
10
11
//答案選項改成虛擬方法
public void TestQuesition1() {
    base.TestQuesition1();
    Console.WriteLine($"答案: {Answer1()}"); //選項改成虛擬方法
}

//增加一個答案的虛擬方法
//目的是讓繼承的子類別重寫方法,因為每個人的答案都不一樣
public virtual string Answer1() {
    return "";
}

結構(範本方法模式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
考題試卷
+ 試題1()
+ 試題2()
+ 試題3()
# 答案1(): string
# 答案2(): string
# 答案3(): string

學生A抄的試卷
# 答案1(): string
# 答案2(): string
# 答案3(): string

學生B抄的試卷
# 答案1(): string
# 答案2(): string
# 答案3(): string

程式碼(範本方法模式)

  1. 新增一個虛擬方法為答案的選項,目的是讓子類別可以重寫該方法,因為每個人的答案都不一樣。
  2. 將父類別的考題題目方法中的答案改成虛擬方法
  3. 用戶端調用,將子類變數的宣告改成「父類別TestPaper」,利用了多型,實現了程式碼的複用。

此時要有更多的學生來答題,只不過是在試卷的範本上填寫選擇題的選項答案,這是每個人的試卷唯一的不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//用戶端程式碼
public static void Main()
{
    //學生A抄的試卷
    Console.WriteLine("學生A抄的試卷");
    //將子類變數的宣告改成父類別TestPaper,利用了多型,實現了程式碼的複用
    TestPaper studentA = new StudentA();
    studentA.TestQuesition1();
    studentA.TestQuesition2();
    studentA.TestQuesition3();

    //學生B抄的試卷
    Console.WriteLine("學生B抄的試卷");
    TestPaper studentB = new StudentB();
    studentB.TestQuesition1();
    studentB.TestQuesition2();
    studentB.TestQuesition3();
}

//父類別-考題試卷
class TestPaper {
    //考試題目
    public void TestQuesition1() {
        Console.WriteLine("題目1");
        Console.WriteLine($"答案:{Answer1()}");
    }
    public void TestQuesition2() {
        Console.WriteLine("題目2");
        Console.WriteLine($"答案:{Answer2()}");
    }
    public void TestQuesition3() {
        Console.WriteLine("題目3");
        Console.WriteLine($"答案:{Answer3()}");
    }
    //答案的虛擬方法
    public virtual string Answer1() {
        return "";
    }
    public virtual string Answer2() {
        return "";
    }
    public virtual string Answer3() {
        return "";
    }
}

//子類別-同學A
class StudentA:TestPaper {
    //重久答案的虛擬方法
    public override string Answer1() {
        return "a";
    }
    public override string Answer2() {
        return "b";
    }
    public override string Answer3() {
        return "c";
    }
}

//子類別-同學B
class StudentB:TestPaper {
    //重久答案的虛擬方法
    public override string Answer1() {
        return "c";
    }
    public override string Answer2() {
        return "a";
    }
    public override string Answer3() {
        return "b";
    }
}
This post is licensed under CC BY 4.0 by the author.

[閱讀筆記][Design Pattern] Ch9.原型模式 Prototype (深淺複製 MemberwiseClone())

[閱讀筆記][Design Pattern] Ch11.迪米特法則(LoD)