如果對原有的類庫做一定程度的擴展,加上自己需要的新功能. 這個問題也在我們的開發中一而再的出現. 如果原來的設計良好,遵循OCP(對修改封閉,對擴展開放), 那么這個問題倒還好解決, 但問題是你不會總是那么好運. 看看下面這個例子.
6 public void UseQueue(MessageQueue q)
7 {
8 q.Send("Hello!");
9 }
在這個例子中UseQueue方法就是一個不利于擴展的設計, 在它的參數中使用了MessageQueue這么一個具體類. 這也意味著UseQueue方法將與MessageQueue綁定. 如果我想換一個Queue ( 比如一個叫IBMMQ的類 )的話, 以上所有的UseQueue方法(這里只是一個例子,可以想象會有很多用到Queue的方法)都無法重用.
此時如何處理? 難道祭起copy/paste大法? 最容易想到的一個辦法就是讓IBMMQ繼承于MessageQueue, 然后override 相關方法的實現. 但是我們知道C# 是單繼承的, 你怎么確保IBMMQ沒有繼承于其他類, 而將這個寶貴的機會讓給MessageQueue? 而且繼承的最大優勢在于重用基類的代碼, 但是現在我只是想獲得一個基類的類型和該類型中的方法名. 確切的說我只是想獲得一個MessageQueue的一個隱式接口,而不是它的實現. Martin Folwer將這個想法描述為 ImplicitInterfaceImplementation.
既然繼承不是一個好的方法, 那么來試試最近很流行的Dynamic Proxy. 讓我們換一個更簡單明了的例子.
11 private static void Speak(Dog dog)
12 {
13 dog.Talk();
14 }
15
16 public class Dog
17 {
18 public virtual void Talk()
19 {
20 Console.Out.WriteLine("arf!");
21 }
22 }
23
24 public class Robot
25 {
26 public virtual void Talk()
27 {
28 Console.Out.WriteLine("Click!");
29 }
30 }
現在我們想讓Speak方法能接受Robot做為參數,去執行Robot的Talk方法. 然而由于靜態類型的限制, 我們是不可能讓Speak方法接受Robot類型的參數的,不過通過Dynamic Proxy我們倒是可以讓Speak去執行Robot的Talk方法.
6 internal class Program
7 {
8 [STAThread]
9 private static void Main()
10 {
11 Dog dog=Dog.DogInstance();
12 Dog robot=Robot.DogInstance();
13
14 Speak(dog);
15 Speak(robot);
16 }
17
18 private static void Speak(Dog dog)
19 {
20 dog.Talk();
21 }
22 }
23
24 public class Dog
25 {
26 public static Dog DogInstance()
27 {
28 return new Dog();
29 }
30 public virtual void Talk()
31 {
32 Console.Out.WriteLine("arf!");
33 }
34 }
35
36 public class Robot
37 {
38 public static Dog DogInstance()
39 {
40 ProxyGenerator generator = new ProxyGenerator();
41 Dog robot = (Dog) generator.CreateClassProxy(typeof (Dog), new RobertInterceptor());
42 return robot;
43 }
44
45 public virtual void Talk()
46 {
47 Console.Out.WriteLine("Click!");
48 }
49 }
50
51 public class RobertInterceptor : StandardInterceptor
52 {
53 public override object Intercept(IInvocation invocation, params object[] args)
54 {
55 if (invocation.Method.Name.Equals("Talk"))
56 {
57 Robot robot=new Robot();
58 robot.Talk();
59 }
60 return null;
61 }
62 }
有關Dynamic Proxy(這里用的是Castle)的內容, 本文不作詳細介紹, 在園子里搜索一下你可以找到相關內容. 從上面的程序的輸出結果中你可以看出通過動態代理我們實現了Speak方法的重用, 讓它可以間接的使用Robot的Talk方法.
上面用Dynamic Proxy的方法雖然可行,但是實在過于繁瑣,而且看上去非常的丑陋. 來看看C++怎么處理這個問題的.
6 class Dog { public: void Talk() { cout << "arf!" << endl; };
7 class Robot { public: void Talk() { cout << "Click!" << endl; };
8
9 template < class T > void Speak( T spkr ) { spkr.Talk(); }
10
11 int main() {
12 Dog d;
13 Robot r;
14 Speak(d);
15 Speak(r):
16 }
由于C++的template沒有類型的約束, 給出一個非常漂亮的解決方案. 不過C#, java的泛型可就望洋興嘆了.
動態語言面對這個問題就更是一笑了之了.
def speak(anything):
anything.talk()
class Dog:
def talk(self): print "arf!"
def reproduce(self): pass
class Robot:
def talk(self): print "Click!"
def oilChange(self): pass
dog = Dog()
robot = Robot()
speak(dog)
speak(robot)
由于Duck Typing的特性, 使得Robot類只需要有一個叫做Talk的方法就可以被調用到,根本不受到參數類型的限制.
Summary:
其實以上的方法都是一種亡羊補牢的辦法. 但是這種情況幾乎是無法避免的.同時你應該思考是什么原因導致了這種問題的產生?
5 public interface ITalkable
6 {
7 void Talk();
8 }
9
10 public class Dog : ITalkable
11 {
12 public virtual void Talk()
13 {
14 Console.Out.WriteLine("arf!");
15 }
16 }
17
18 public class Robot : ITalkable
19 {
20 public virtual void Talk()
21 {
22 Console.Out.WriteLine("Click!");
23 }
24 }
25
26 internal class Program
27 {
28 [STAThread]
29 private static void Main()
30 {
31 ITalkable dog = new Dog();
32 ITalkable robot = new Robot();
33 Speak(dog);
34 Speak(robot);
35 }
36
37 private static void Speak(ITalkable talker)
38 {
39 talker.Talk();
40 }
41 }
如果這樣做你還會有以上的問題嗎? Design to interface.可以說是面向對象的核心概念之一. 你應該盡可能得將Contract和Implement分離開來. COM就強制你必須這么做. C#,Java給了你自由,它沒有強制你這么做, 但是你應該盡可能這么做, 不然你就象最開始那個例子,被MessageQueue限制死了,也使得很多的使用了MessageQueue的代碼無法得到重用.
聽說過Web Service嗎? Contract都用xml(WSDL)來定義了.
浙公網安備 33010602011771號