Delegate Class :: 沉思者
来源: BlogBus 原始链接: http://www.blogbus.com:80/blogbus/blog/diary.php?diaryid=529253 存档链接: https://web.archive.org/web/20050106210556id_/http://www.blogbus.com:80/blogbus/blog/diary.php?diaryid=529253
沉思者 我思故我在 <<<Package vs. Namespace | 主页 | 属性也应是接口>>> Delegate Class 2004-12-06 我们先来看问题: 有一个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。 darwin_yuan 发表于 2004-12-06 12:20 引用Trackback(0) | 编辑 Comments 发表评论 最近更新 为goto正名 相等性判断的自动化 属性也应是接口 Delegate Class Package vs. Namespace Function Signature:新思维 Visibility: 两种观点 Java在Interface方面的缺陷 也谈对象的生命 Unlink操作