Chinaunix首页 | 论坛 | 博客
  • 博客访问: 133631
  • 博文数量: 69
  • 博客积分: 2895
  • 博客等级: 少校
  • 技术积分: 710
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-03 18:05
文章分类

全部博文(69)

文章存档

2010年(69)

我的朋友

分类:

2010-09-04 22:54:59

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

        既然继承不是一个好的方法, 那么来试试最近很流行的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)来定义了.
阅读(464) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~