(原創) 我的Design Pattern之旅[4]:使用Generic改進Strategy Pattern (OO) (Design Pattern) (.NET) (C#)
Abstract
在(原創) 我的Design Pattern之旅:使用template改進Strategy Pattern (OO) (Design Pattern) (C/C++) (template)中,使用了C++的template改進strategy pattern,本文使用C#的generic改進strategy pattern。
Introduction
C# 2.0加入了generic對泛型的支援,所以想將原來C++的template程式一行一行的改成C# generic。
在strategy pattern中,通常為了讓strategy能完全存取物件的public method/property,我們會利用傳this將整個物件傳給strategy
public void drawShape() {
this.shape.draw(this);
}
為了達成此需求,我們的interface須如此定義
interface IShape {
void draw(Grapher grapher);
}
完整的程式碼如下

/* 2
(C) OOMusou 2007 http://oomusou.cnblogs.com3

4
Filename : DP_StrategyPattern3_polymorphism_this.cs5
Compiler : Visual Studio 2005 / C# 2.06
Description : Demo how to use Strategy Pattern with this7
Release : 04/07/2007 1.08
*/9
using System;10

11
interface IShape {12
void draw(Grapher grapher);13
}14

15
class Grapher {16
private IShape shape;17
private string text;18
19
public Grapher() { }20
public Grapher(IShape shape) : this(shape, "Hello Shape!!") { }21
public Grapher(IShape shape, string text) {22
this.shape = shape;23
this.text = text;24
} 25
26
public void drawShape() {27
this.shape.draw(this);28
}29
30
public void setShape(IShape shape, string text) {31
this.text = text;32
this.shape = shape;33
}34
35
public string getText () {36
return this.text;37
}38
}39

40

41
class Triangle : IShape {42
public void draw(Grapher grapher) {43
Console.WriteLine("Draw {0:s} in Triangle", grapher.getText());44
}45
}46

47
class Circle: IShape {48
public void draw(Grapher grapher) {49
Console.WriteLine("Draw {0:s} in Circle", grapher.getText());50
}51
}52

53
class Square : IShape {54
public void draw(Grapher grapher) {55
Console.WriteLine("Draw {0:s} in Square", grapher.getText());56
}57
}58

59
class main {60
public static void Main() {61
Grapher theGrapher = new Grapher(new Square());62
theGrapher.drawShape();63

64
theGrapher.setShape(new Circle(), "Hello C#!!");65
theGrapher.drawShape();66
}67
}
執行結果
Draw Hello Shape!! in Square
Draw Hello C#!! in Circle
這樣的設計看似完美,但淺在一個問題
interface IShape {
void draw(Grapher grapher);
}
規定了draw()一定要傳Grapher型別進去,若將來因為需求改變,又多了一個Painter class,且和Grapher毫無相關,既非繼承亦非多型,但又想使用這些strategy,但因為IShape已規定只能傳Grapher型別,所以Painter無法繼續使用IShape interface。
在(原創) 我的Design Pattern之旅[3]:使用template改進Strategy Pattern (OO) (Design Pattern) (C/C++) (template)中,我們已成功使用C++的template解決這種型別被限制的問題,所以我們試著也使用C#的Generic來解決。
首些我們先將IShape改用Generic
interface IShape<T> {
void draw(T grapher);
}
接著其他實做IShape<T>的程式亦需改寫成
class Triangle<T> : IShape<T> where T : IGrapher {
public void draw(T grapher) {
Console.WriteLine("Draw {0:s} in Triangle", grapher.getText());
}
}
C#多了where這個keyword,這是我卡最久的地方,也是C# generic和C++ template不同之處,C++ template並沒有限定泛型的型別,但C# generic的泛型是『有限的汎型』,或稱『以interface為基礎的汎型』、『強型別泛型』,也就是說,C#泛型不能像C++泛型那樣天馬行空的汎型,C#泛型必須『限制』在interface下,所以where稱為『constraint』
where T : IGrapher
表示泛型T需限制在IGrapher的interface下
也因此,因為各strategy會用到物件的getText(),所以我們定義IGrapher interface一定要有getText()
interface IGrapher {
string getText();
}
日後若有Painter class也想使用IShape的strategy,只要也實做IGrapher即可。
完整程式碼如下

/* 2
(C) OOMusou 2007 http://oomusou.cnblogs.com3

4
Filename : DP_StrategyPattern3_polymorphism_this.cs5
Compiler : Visual Studio 2005 / C# 2.06
Description : Demo how to use Strategy Pattern with this by Generic7
Release : 04/07/2007 1.08
*/9
using System;10

11
interface IGrapher {12
string getText();13
}14

15
interface IShape<T> {16
void draw(T grapher);17
}18

19
class Grapher : IGrapher {20
private IShape<Grapher> shape;21

22
private string text;23

24
public Grapher() { }25
public Grapher(IShape<Grapher> shape) : this(shape, "Hello Shape!!") { }26
public Grapher(IShape<Grapher> shape, string text) {27
this.shape = shape;28
this.text = text;29
} 30

31
public void drawShape() {32
this.shape.draw(this);33
}34
35
public void setShape(IShape<Grapher> shape, string text) {36
this.text = text;37
this.shape = shape;38
}39
40
public string getText () {41
return this.text;42
}43
}44

45
class Triangle<T> : IShape<T> where T : IGrapher {46
public void draw(T grapher) {47
Console.WriteLine("Draw {0:s} in Triangle", grapher.getText());48
}49
}50

51
class Circle<T> : IShape<T> where T : IGrapher{52
public void draw(T grapher) {53
Console.WriteLine("Draw {0:s} in Circle", grapher.getText());54
}55
}56

57
class Square<T> : IShape<T> where T : IGrapher {58
public void draw(T grapher) {59
Console.WriteLine("Draw {0:s} in Square", grapher.getText());60
}61
}62

63
class main {64
public static void Main() {65
Grapher theGrapher = new Grapher(new Square<Grapher>());66
theGrapher.drawShape();67
68
theGrapher.setShape(new Circle<Grapher>(), "Hello C#!!");69
theGrapher.drawShape();70
}71
}
執行結果
Draw Hello Shape!! in Square
Draw Hello C#!! in Circle
Conclusion
C#泛型有什麼好處呢?
where T : IGrapher, IPainter
where後面的限制,並不是只能接一個interface或class而已,還能繼續接,若以後要擴充泛型T的使用,只要繼續接下去即可,如此的寫法已經比物件導向的多型還強,多型限定只能在一個interface或abstract class的多型物件體系,但泛型可以是多個interface或多個abstract class的物件體系,所以泛型可視為『更強的多型』。
假如你同時懂C++和C#,或許會覺得用C#泛型解決strategy pattern『不是那麼漂亮』!!因為若還要用interface,我大可這樣寫
/* 2
(C) OOMusou 2007 http://oomusou.cnblogs.com3

4
Filename : DP_StrategyPattern3_polymorphism_this_interface.cs5
Compiler : Visual Studio 2005 / C# 2.06
Description : Demo how to use Strategy Pattern with this by interface7
Release : 04/07/2007 1.08
*/9
using System;10

11
interface IGrapher {12
string getText();13
}14

15
interface IShape {16
void draw(IGrapher grapher);17
}18

19
class Grapher : IGrapher {20
private IShape shape;21

22
private string text;23

24
public Grapher() { }25
public Grapher(IShape shape) : this(shape, "Hello Shape!!") { }26
public Grapher(IShape shape, string text) {27
this.shape = shape;28
this.text = text;29
}30

31
public void drawShape() {32
this.shape.draw(this);33
}34

35
public void setShape(IShape shape, string text) {36
this.text = text;37
this.shape = shape;38
}39

40
public string getText() {41
return this.text;42
}43
}44

45
class Triangle : IShape {46
public void draw(IGrapher grapher) {47
Console.WriteLine("Draw {0:s} in Triangle", grapher.getText());48
}49
}50

51
class Circle : IShape {52
public void draw(IGrapher grapher) {53
Console.WriteLine("Draw {0:s} in Circle", grapher.getText());54
}55
}56

57
class Square : IShape {58
public void draw(IGrapher grapher) {59
Console.WriteLine("Draw {0:s} in Square", grapher.getText());60
}61
}62

63
class main {64
public static void Main() {65
Grapher theGrapher = new Grapher(new Square());66
theGrapher.drawShape();67

68
theGrapher.setShape(new Circle(), "Hello C#!!");69
theGrapher.drawShape();70
}71
}
沒錯,這也是C#泛型的一個限制,『一個基於interface或class的泛型』,這種『強型別泛型』,好處是讓泛型也可有intelliSense,且在compile-time就能發現錯誤,不像C++屬於『弱型別泛型』,常得在run-time才能發現錯誤,所以C#泛型較C++穩定,不過也由於限制過多,所以比較沒有泛型的味道,以我個人來說,我比較喜歡C++泛型,因為既然要泛型,就是放諸四海皆準,擺脫型別的限制,擺脫interface的枷鎖,而不是到最後還是得靠interface。
雖然如此,仍就可將C#泛型看成『更強的多型』,很多原本多型設計,可以考慮用泛型讓設計應用更廣。
See Also
(原創) 我的Design Pattern之旅[3]:使用template改進Strategy Pattern (OO) (Design Pattern) (C/C++) (template)
(轉貼) Anders Hejlsberg談C#、Java和C++中的泛型 (,NET) (C#)


浙公网安备 33010602011771号