如果对原有的类库做一定程度的扩展,加上自己需要的新功能. 这个问题也在我们的开发中一而再的出现. 如果原来的设计良好,遵循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)来定义了.