享受代码,享受人生

SOA is an integration solution. SOA is message oriented first.
The Key character of SOA is loosely coupled. SOA is enriched
by creating composite apps.
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Separate Contract from Implementation

Posted on 2006-01-12 14:06  idior  阅读(3888)  评论(9编辑  收藏  举报
        在平时的开发中, 如果你接到的是一个全部由自己团队开发的项目, 那么恭喜你, 你将摆脱很多让人烦恼的问题. 然而事情总非如此, 在新项目中使用已有的第三方类库, 甚至在已有的遗留系统基础上做开发, 这种事情已经成为必然.
        如果对原有的类库做一定程度的扩展,加上自己需要的新功能. 这个问题也在我们的开发中一而再的出现. 如果原来的设计良好,遵循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方法.          

             
Tip一个类A和另一个类B发生关联, 有两种形式: 1. A创建B     2: A使用B
那么请你考虑遵循下面的原则:
A
要么创建B,要么使用B.不要同时创建并使用B.
这样可以为你的代码带来更强的扩展性(方便的替换B的实例).

   32     class A

   33     {

   34         public void Method()

   35         {

   36             //Bad way

   37             //B b=new B();

   38             //b.Method1();

   39 

   40             //Good way

   41             B b=B.GetInstance();

   42             b.Method1();

   43         }

   44     }

         上面用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)来定义了.