• Separate Contract from Implementation


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

  • 相关阅读:
    为什么你投十份简历,只有一两家公司约你?又或者为什么你每投一份简历都能获得面试机会?
    JAVA程序1,1,2,3,5,8,13,21....第30个是什么...?
    1-2+3-4+5-6+7......+n的几种实现
    分层应用——如何实现登录?
    初识三层
    VB.NET视频总结——后续篇
    操作系统小结
    VB.NET视频总结——基础篇
    台湾与大陆的计算机术语翻译差异
    VB.NET概述
  • 原文地址:https://www.cnblogs.com/idior/p/315947.html
Copyright © 2020-2023  润新知