锁定老帖子 主题:如何减少子类对超类的依赖——一个设计问题
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2008-03-27
一般来说,根据所谓好莱坞原则,我们不应该在子类中显示调用超类的方法,而是通过重写超类的方法来实现特殊的逻辑,以此来避免循环依赖。不过,调用超类中被重写的同名方法,通常是可以接受的,比如: Class A { public void go() { System.out.println("do by A"); } } Class B extends A { @Override public void go() { System.out.println("do by B"); super.go(); } }
如果超类要做的事,总在子类之前,或者之后,是没有问题的,而一旦子类do的前后各需要一段公共的代码,这个办法就行不通了。
于是我们干脆把一个方法拆成两个,其中一个“大”方法调用另一个“小”方法,由子类去重写那个小方法,这样子类干脆就不用掉父类的方法了,显得更加纯粹:
abstract Class A { public void go() //大方法 { prepare() going(); clearup() } abstract public void going(); //小方法 protected void prepare() { System.out.println("prepare by A"); } protected void clearup() { System.out.println("clearup by A"); } } Class B extends A { @Override public void going() //重写小方法 { System.out.println("do by B"); } }
问题是,如果Class B还有子类呢,它自己也需要在子类的执行逻辑前后插入一些东西,难道又把doing这个“小”函数拆开?这是不是太复杂了点?再说我都不知道怎么跟下面的“小小”函数起名字了 。当然,让子类反过来调Class B的函数更不好,不但违反了前面的“Don't call me”原则,而且本身就很麻烦——每个子类都得写。
有没有更好的办法呢? 看来这个问题没写清楚,引起了一些误会,有必要进一步解释一下。首先,我这里并不是想验证或者实践某种模式,确实是一个实际的开发项目遇到了需要权衡的地方。
目前我的设计大致是像下面的样子:
只所以选用继承结构,是因为子类所代表的几个概念与父类在自然意义上确实是"is a"的关系,并且在子类间是互斥的;而且A/B/C这几个类都很稳定,可能发生的变化主要是B可能会增加子类B3,B4,或者A会增加子类D,E之类的。
使用它们的客户程序在获得一个实例之后,一般情况下只会调用它们的go方法,也就是说,不关心具体的实例是属于哪种类型的。当然,有很多方式都可以实现这个需求,而我关心的是,怎样才能让可能新增的B3、B4、D、E这些类实现起来最简单、可靠。所以,最终还是选择了逐步细分函数的方式。
代码如下: abstract Class A { public void go() //大方法 { prepare() going(); clearup() } abstract public void going(); //小方法 protected void prepare() { System.out.println("prepare by A"); } protected void clearup() { System.out.println("clearup by A"); } } abstract Class B extends A { @Override public void going() //重写小方法 { beforeRun(); run(); //小小方法 afterRun(); } abstract public void run(); protected void beforeRun() { System.out.println("beforeRun by B"); } protected void afterRun() { System.out.println("beforeRun by B"); } } Class B1 extends B { @Override public void run() //重写小小方法 { System.out.println("go by B1"); } } Class B2 extends B { @Override public void run() //重写小小方法 { System.out.println("go by B2"); } } Class C extends A { @Override public void going() //重写小方法 { System.out.println("go by C"); } }
这样,B在自己的层次,“要求”本类别的Class在go的时候,必须按顺序调用beforeRun和afterRun,新增的B?子类只需实现自己的run方法即可被正确使用;万一真有很特殊的情况,新的子类希望在go的时候不要调用beforeRun或者afterRun,那么用一个空函数覆写它们即可。
反之,如果beforeRun/afterRun这样的函数需要B?子类来调用,那么绝大多数子类都要编写调用的代码,也包括将来可能扩充的,这显然造成了一定的代码重复。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2008-03-27
看看装饰者模式吧,调用前添加一些自己的动作
|
|
返回顶楼 | |
发表时间:2008-03-28
spring AOP解决方式
或把B匿名 引用 A b =new A(){
public going(){} } |
|
返回顶楼 | |
发表时间:2008-03-28
HRoger:
谢谢你的建议。 不过,我关心的是如何在一棵继承树中,实现对一个方法调用的按层次“包装”,这是因为例子中的几个类,在概念上是天然符合继承关系的,而且每个层次需要“包装”的内容,也是确定的。 如果采用decorator模式,就意味我还需要再建立一个decorator类,并且使用该类来“代理”go请求,这样的复杂度似乎比拆分“小”函数还要高;而且,在继承树中当中使用,确实不太方便。 当然,如果完全抛弃继承结构,而采用decorator来层层“包装”,确实可以得到更好的动态灵活性,但这些灵活性是当前用不到的,同时,丧失了继承带来的清晰和简洁(可以继承的不只go一个方法)。 所以,目前看来decorator模式还不是更优的选择。 |
|
返回顶楼 | |
发表时间:2008-03-28
唉.........你这样用到底是什么目的,可读?可扩展......亦或是为了实现这个模式?
|
|
返回顶楼 | |
发表时间:2008-03-28
To 抛出异常的爱:
AOP的方式对于我这个简单需求来说太重了些。 关于匿名类,我没太理解,如何能解决这个问题呢,那个b要定义在什么地方呢?可能是我不够熟悉匿名类的机制吧。 能否帮我讲得再详细一些,谢谢! |
|
返回顶楼 | |
发表时间:2008-03-28
To C3PO:
老兄这个观点我不大同意:继承确实是类之间静态关系最强的一种,但如果子类能不去“了解”父类中比较一般的逻辑,而专注与自己的“特殊”逻辑,显然会让整个代码更有弹性,也更清晰。何况,在我这个情景中,如果由子类去调父类的prepare之类的方法,就意味着每个子类都得同样的写法,代码重复不说,对以后可能出现的新的子类如何实现go方法,也提出了额外的限制。 |
|
返回顶楼 | |
发表时间:2008-03-29
关于继承,我还是很推崇<Effictive JAVA>里面的说法:
引用 除非專為繼承而設計並提供文件,否持不要使用繼承 所以,如果设计一个类时,需要考虑到继承,就应该考虑到各种可能,并把父类中各个方法的依赖性用文档详细说明.就如Collection Framework里的那些Abstract XXX一样. 否则,要么把该类设计为final的,要么在说明文档中注明:该类不是为了继承而设计的 |
|
返回顶楼 | |
发表时间:2008-04-01
原来对于使用情景交代得不太清楚,现在补充说明了一下,还请大家指正
|
|
返回顶楼 | |
发表时间:2008-04-01
我觉得楼主有点走火入魔了。。
父类的一个方法在子类进行重写,本身就是一个封装性很强的一个东西,里面的实现是很独立的。如果不断的考虑将其共有的东西抽象,分拆,就会走进一个死胡同。。 我做的话,要对这些方法内的共有代码冗余置之不理(毕竟都是实现细节的问题),要是真的与心不忍,就将它们抽到一个Utility类去。。 这样还解决不了,就真的是本身方法的划分有问题了,粒度过大?重构吧 |
|
返回顶楼 | |