手机站
网通分站
电信主站
密 码:
用户名:
当前位置 : 主页>程序设计>Java技术>列表

主题:如何减少子类对超类的依赖——一个设计问题

来源: 作者: 时间:2008-04-02
西部数码-全国虚拟主机10强!40余项虚拟主机管理功能,全国领先!双线多线虚拟主机南北访问畅通无阻!免费赠送企业邮局,.CN域名,自助建站480元起,免费试用7天,满意再付款! P4主机租用799元/月.月付免压金!

一般来说,根据所谓好莱坞原则,我们不应该在子类中显示调用超类的方法,而是通过重写超类的方法来实现特殊的逻辑,以此来避免循环依赖。不过,调用超类中被重写的同名方法,通常是可以接受的,比如:

Java代码 复制代码
  1. Class A   
  2. {   
  3.   public void go()   
  4.   {   
  5.      System.out.println("do by A");   
  6.   }   
  7. }   
  8.   
  9. Class B extends A   
  10. {   
  11.   @Override  
  12.   public void go()   
  13.   {   
  14.      System.out.println("do by B");   
  15.      super.go();   
  16.   }   
  17. }  

 

如果超类要做的事,总在子类之前,或者之后,是没有问题的,而一旦子类do的前后各需要一段公共的代码,这个办法就行不通了。

 

于是我们干脆把一个方法拆成两个,其中一个“大”方法调用另一个“小”方法,由子类去重写那个小方法,这样子类干脆就不用掉父类的方法了,显得更加纯粹:

 

Java代码 复制代码
  1. abstract Class A   
  2. {   
  3.   public void go() //大方法   
  4.   {   
  5.      prepare()   
  6.      going();   
  7.      clearup()   
  8.   }   
  9.   
  10.   abstract public void going(); //小方法   
  11.   
  12.   protected void prepare()   
  13.   {   
  14.      System.out.println("prepare by A");   
  15.   }   
  16.   
  17.   protected void clearup()   
  18.   {   
  19.      System.out.println("clearup by A");   
  20.   }   
  21. }   
  22.   
  23. Class B extends A   
  24. {   
  25.   @Override    
  26.   public void going() //重写小方法   
  27.   {   
  28.      System.out.println("do by B");   
  29.   }   
  30. }  

 

问题是,如果Class B还有子类呢,它自己也需要在子类的执行逻辑前后插入一些东西,难道又把doing这个“小”函数拆开?这是不是太复杂了点?再说我都不知道怎么跟下面的“小小”函数起名字了wink 。当然,让子类反过来调Class B的函数更不好,不但违反了前面的“Don't call me”原则,而且本身就很麻烦——每个子类都得写。

 

有没有更好的办法呢?  看来这个问题没写清楚,引起了一些误会,有必要进一步解释一下。首先,我这里并不是想验证或者实践某种模式,确实是一个实际的开发项目遇到了需要权衡的地方。

 

目前我的设计大致是像下面的样子:

 

设计问题类图

 

只所以选用继承结构,是因为子类所代表的几个概念与父类在自然意义上确实是"is a"的关系,并且在子类间是互斥的;而且A/B/C这几个类都很稳定,可能发生的变化主要是B可能会增加子类B3,B4,或者A会增加子类D,E之类的。

 

使用它们的客户程序在获得一个实例之后,一般情况下只会调用它们的go方法,也就是说,不关心具体的实例是属于哪种类型的。当然,有很多方式都可以实现这个需求,而我关心的是,怎样才能让可能新增的B3、B4、D、E这些类实现起来最简单、可靠。所以,最终还是选择了逐步细分函数的方式。

 

代码如下:

Java代码 复制代码
  1. abstract Class A   
  2. {   
  3.   public void go() //大方法   
  4.   {   
  5.      prepare()   
  6.      going();   
  7.      clearup()   
  8.   }   
  9.   
  10.   abstract public void going(); //小方法   
  11.   
  12.   protected void prepare()   
  13.   {   
  14.      System.out.println("prepare by A");   
  15.   }   
  16.   
  17.   protected void clearup()   
  18.   {   
  19.      System.out.println("clearup by A");   
  20.   }   
  21. }   
  22.   
  23. abstract Class B extends A   
  24. {   
  25.   @Override    
  26.   public void going() //重写小方法   
  27.   {   
  28.      beforeRun();   
  29.      run(); //小小方法   
  30.      afterRun();   
  31.   }   
  32.      
  33.   abstract public void run();   
  34.      
  35.   protected void beforeRun()   
  36.   {   
  37.      System.out.println("beforeRun by B");   
  38.   }   
  39.   
  40.   protected void afterRun()   
  41.   {   
  42.      System.out.println("beforeRun by B");   
  43.   }   
  44. }   
  45.   
  46. Class B1 extends B   
  47. {   
  48.   @Override    
  49.   public void run() //重写小小方法   
  50.   {   
  51.      System.out.println("go by B1");   
  52.   }   
  53. }   
  54.   
  55. Class B2 extends B   
  56. {   
  57.   @Override    
  58.   public void run() //重写小小方法   
  59.   {   
  60.      System.out.println("go by B2");   
  61.   }   
  62. }   
  63.   
  64. Class C extends A   
  65. {   
  66.   @Override    
  67.   public void going() //重写小方法   
  68.   {   
  69.      System.out.println("go by C");   
  70.   }   
  71. }  

 

 

这样,B在自己的层次,“要求”本类别的Class在go的时候,必须按顺序调用beforeRun和afterRun,新增的B?子类只需实现自己的run方法即可被正确使用;万一真有很特殊的情况,新的子类希望在go的时候不要调用beforeRun或者afterRun,那么用一个空函数覆写它们即可。

 

反之,如果beforeRun/afterRun这样的函数需要B?子类来调用,那么绝大多数子类都要编写调用的代码,也包括将来可能扩充的,这显然造成了一定的代码重复。

文章整理:西部数码--专业提供域名注册虚拟主机服务
http://www.west263.com
以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!