Function Signature:新思维- -| 回首页 | 2005年索引 | - -属性也应该是接口

Delegate Class- -

                                      

Delegate Class? What is it?

我们先来看问题:

有一个concrete class,它有两个职责R1和R2。按照SRP(Single Responsibility Principle),我们需要把这两个职责放到两个抽象类中,然后由它来继承/实现这两个抽象类。

在这两个职责类中,都存在着需要concrete class实现的抽象方法,以及自身的模板方法。如下:

public abstract class R1 {
   public abstract void r1m1();
   public abstract void r1m2();
   public abstract void r1m3();
  
   // template method
   public void r1m() {
       r1m1();
       r1m2();
       r1m3();
   }
}

public abstract class R2 {
   public abstract void r2m1();
   public abstract void r2m2();

   // template method
   public void r2m() {
      r2m1();
      r2m2();
   }
}


由于Java/C#等语言只允许单一继承,我们不得不把至少一个职责实现为接口。那么我们不妨把R2实现为接口。另外,由于这些语言不允许在接口中定义非抽象方法,我们不得不另作选择:

1.把模版方法也定义为抽象的,由实现类来实现它。

public interface R2 {
   void r2m1();
   void r2m2();
   void r2m();
}

public class Foo extends R1 implements R2 {
   ...
   public void r2m1() { ... }
   public void r2m2() { ... }

   public void r2m() {
      r2m1();
      r2m2();
   }
}

这种方法看起来可以解决问题,但事实上是一种非常糟糕的方案,因为:

首先,接口R2并不仅仅被Foo实现,它还可能被其他的类实现。如果使用这种方法,将不得不把r2m的实现在每个实现类中拷贝一份。

更重要的是,从概念抽象而言,r2m并不是一个需要实现类实现的方法,它与那些抽象方法处于不同的层次和范畴。把r2m的实现放到Foo中,等于把两个层次的问题耦合到了一起。

2、把r2m从R2中拿出来,放到client中。

public interface R2 {
   void r2m1();
   void r2m2();
}

public class Foo extends R1 implements R2 {
   ...
   public void r2m1() { ... }
   public void r2m2() { ... }
}

public class Client {
   private R2 serviceProvider;

   public void doSomething() {
      ...
      r2m();
      ...
   }
  
   public void r2m() {
     serviceProvider.r2m1();
     serviceProvider.r2m2();
   }
   ...
}

这种方法就是典型的Strategy模式,Client拥有一个接口R2的实现类的实例,R2的不同实现者通过实现R2的抽象方法来提供不同strategy,而client则通过r2m中定义的算法来使用某种strategy。

问题在于,这种方法把client与r2m所提供的算法耦合到一起了。而事实上,r2m本来是独立于任何client的。如果用这种方法,当存在多个client的情况下,我们不得不把r2m中的算法在每个client中都要实现一遍。Bad smell, right?

3、把r2m放到一个中间类中。

class R2Algorithm {
   private R2 serviceProvider;

   public void r2m() {
     serviceProvider.r2m1();
     serviceProvider.r2m2();
   }

   public R2Algorithm(R2 sp) {
     serviceProvider = sp;
   }
}

public class Client {
   private R2Algorithm r2;
  
   public void doSomething() {
     ...
     r2.r2m();
     ...
   }
}

这种方法破除了接口,算法,以及client之间的耦合关系,让系统变得灵活和便于修改。但代价是,Client不得不创建一个额外的R2Algorithm对象。仔细观察一下就会得知,这个对象是根本没有必要存在的,因为它仅仅提供了算法。所以,我们可以:

4、把r2m放到一个工具类中。

class R2Algorithm{
   public static void r2m(R2 sp) {
      sp.r2m1();
      sp.r2m2();     
   }
}

public class Client {
   private R2 serviceProvider;

   public void doSomething() {
      ...
      R2Algorithm.r2m(ServiceProvider);
      ...
   }
  
   ...
}

这是使用Java语言所能找到的最好的解决方案。

对于这种模式化的概念,我们可以把中间的工具类抽象为一个接口委托的概念。一个接口委托就是这样一个工具类,它本身委托了一个接口,然后通过访问接口方法来实现算法。它的特性为:

? 委托类只能委托自一个接口;
? 一个interface可以被任意多个委托类委托;
? 委托类本身不能被实例化;
? 委托类和它所委托的接口之间可以实现自动类型转化;
? 委托类内部仅仅可以定义static的变量;
? 委托类可以被继承,继承类仍然为父类所委托接口的委托类;
? 一个委托类不能继承自一个非委托类;
? Client通过委托类除了可以访问委托类的方法,还可以直接访问接口中的方法.

我们来看一个完整的例子:

public interface R2 {
   void r2m1();
   void r2m2();
}

public class Foo extends R1 implements R2 {
   ...
   public void r2m1() { ... }
   public void r2m2() { ... }
   ...
}

// 通过delegates关键字建立起委托关系
public class R2Algrithm delegates R2 {
   public void r2m() {
      r2m1();
      r2m2();
   }
}

public class Client {
   // 注意这里,一个Foo对象可以自动转化为委托类R2Algorithm类型
   private R2Algorithm serviceProvider = new Foo();

   public void doSomething() {
      ...
   serviceProvider.r2m1(); // 直接访问接口方法
      serviceProvider.r2m(); // 访问委托中的算法
      ...
   }     
}

通过委托类,程序员可以更加直接无缝的使用针对接口的算法,并且鼓励设计师对系统进行更加良好的设计。

当前的语言并不直接支持这种概念,Dominoo will。

- 作者: 上帝没发笑 2005年01月31日, 星期一 04:31 加入博采

Trackback

你可以使用这个链接引用该篇文章 http://publishblog.blogchina.com/blog/tb.b?diaryID=650136

回复

评论内容: