如何减少子类对超类的依赖——一个设计问题
一般来说,根据所谓好莱坞原则,我们不应该在子类中显示调用超类的方法,而是通过重写超类的方法来实现特殊的逻辑,以此来避免循环依赖。不过,调用超类中被重写的同名方法,通常是可以接受的,比如:
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?子类来调用,那么绝大多数子类都要编写调用的代码,也包括将来可能扩充的,这显然造成了一定的代码重复。
评论
楼主提到了好莱坞原则,猜测是想应用Template Method模式吧。不过好像没有体会好莱坞原则的重点,好莱坞原则的关键是好莱坞对项目的完全控制,演艺人员只能服从好莱坞领导的安排,在需要的的时候完成自己的表演。
在这个例子里Class A的go()方法定义好顶层的逻辑,换句话说go()是模板方法,最好应该定义成final防止子类重写。Class A的prepare()和clearup() 方法属于顶层逻辑里的具体实现,最好也定义成final防止子类给他置换掉。Class A的going()抽象方法是要求子类实现的。设计好Class A后,生成A的子类时只需要考虑要去置换掉哪些操作,这样会使各个层次类的责任变的很清晰。Template Method模式在设计框架的时候会经常用到。
如果楼主事先说明Template Method模式也许效果会好些
懂点设计的人都知道继承不能乱用,但是并不代表不用
以上无论对错希望大家以一个有好的气氛来探讨
为何会被这么多人唾弃,
很明显是LZ应用环境描述不清.
工厂方法模式也是利用了继承,并且还实现了灵活性.
可见,继承并不是不好,关键是看你如何用.
任何东西的存在都有其道理.
问题是:你懂得利用吗?
支持楼主,你的原意是无可厚非的,很好。
只是要描述清楚了.:)
楼主的问题的由来,就是滥用了继承。
首先有接口
IGoAroundInterceptor
before();
after();
AGoAroundInterceptor implements IGoAroundInterceptor
A类
go() {
IGoAroundInterceptor interceptorget = getGoAroundInterceptor()
interceptorget.before()
run()
interceptorget.after()
}
abstract run();
IGoAroundInterceptor getGoAroundInterceptor() {
return AGoAroundInterceptor.INSTANCE;//简单起见
}
要增加A的子类B,可先增加
BGoAroundInterceptor extends AGoAroundInterceptor
before() {
super.before();
.....//special before
}
after() {
.....//special after
super.after();
}
B类
run() {
}
IGoAroundInterceptor getGoAroundInterceptor() {
return BGoAroundInterceptor.INSTANCE;
}
对B的子类C类似,而且CGoAroundInterceptor可以决定before/after的策略,可以继承AGoAroundInterceptor也可以继承BGoAroundInterceptor,非常自由
实际应用中可能还要考虑IGoAroundInterceptor的初始化等问题,这里只是简单起见介绍一个思路
这样没有必要。 设计模式无非是代码的优美有效的组织形式。
楼主 提出了一个 模式(或者说一个模式片断),并对这个模式片段进行了阐述。这对以后真实的设计有很好的参考意义。
一定要和那个设计模式模式比的话,我觉得范了个错误,因为模式是设计有效参考,但不是来套设计的(当然有些模式的确有很好的可套用性)。
可能大家的疑虑是 这样的一个设计模式片段,在实际设计中有什么应用,应用的方式是什么?
那是另一个问题,不见得要楼主一起给出。
(模式的提出和模式的应用,就好像 电脑 和 电脑的应用 一样,造电脑的人 不见得要知道 电脑应用在什么方面效益最好)
支持楼主的独立见解!支持楼主!!!
就算lz这样的做法是避免所谓的循环依赖,但是lz真的是觉得这种继承真的“清晰和简洁”吗? A的go前后是prepare()和cleanup(),B中又增减了run,假如你又需要新的动作,那么子类里最后到底有多少这种差异性的方法? lz也说假如有D,E。So,清晰性和简洁性都是无从谈起。不仅仅是类爆炸了,方法也在爆炸。
当然在没有知道lz所面对的到底是一种什么样的应用场景下,说decorator可以搞定也有些不太合适。所以建议lz重新考虑下自己的应用需求和设计。你的这些BCDE到底和A应不应是种继承关系?lz到底需要的一种树形结构还是别的什么? 因为我看lz的继承只是为了从父类那里继承个方法。
如果真的是继承关系,那么到底是不是该有对象自己决定该怎么做,还是把那些go啊run转交给别的对象。
其实lz拒绝decorator的理由并不充分,使用decorator绝不会比你现在这种方式更“重”。另外所谓的好莱坞原则是该用到父子类上的吗?
建议多用用组合的方式把
这种方法类似于Spring调用Hibernate的HibernateDaoSupport
LZ你看设计模式的时候一定看过一个叫“单一职能原则”的吧。一个类只做一件事情。
不能为了设计模式而设计模式,所谓的模式是在有大量编码经验基础上总结出来的经验。属于经验之谈,每个都有自己特定的使用环境。
其实我觉得让你的代码保存简约精致就是最好的模式。就是说明明一个System.out.println("Hello, world.");就能解决的问题,不用非要去用什么工厂模式、单例模式、……模式什么的吧。
父类的一个方法在子类进行重写,本身就是一个封装性很强的一个东西,里面的实现是很独立的。如果不断的考虑将其共有的东西抽象,分拆,就会走进一个死胡同。。
我做的话,要对这些方法内的共有代码冗余置之不理(毕竟都是实现细节的问题),要是真的与心不忍,就将它们抽到一个Utility类去。。
这样还解决不了,就真的是本身方法的划分有问题了,粒度过大?重构吧
除非專為繼承而設計並提供文件,否持不要使用繼承
所以,如果设计一个类时,需要考虑到继承,就应该考虑到各种可能,并把父类中各个方法的依赖性用文档详细说明.就如Collection Framework里的那些Abstract XXX一样.
否则,要么把该类设计为final的,要么在说明文档中注明:该类不是为了继承而设计的
老兄这个观点我不大同意:继承确实是类之间静态关系最强的一种,但如果子类能不去“了解”父类中比较一般的逻辑,而专注与自己的“特殊”逻辑,显然会让整个代码更有弹性,也更清晰。何况,在我这个情景中,如果由子类去调父类的prepare之类的方法,就意味着每个子类都得同样的写法,代码重复不说,对以后可能出现的新的子类如何实现go方法,也提出了额外的限制。
发表评论
提醒: 该博客已发表在公共论坛,博客所有留言会成为论坛回贴,留言请注意遵守论坛发贴规则
- 浏览: 9537 次

- 详细资料
搜索本博客
我的相册
共 1 张
最近加入圈子
最新评论
-
如何减少子类对超类的依赖 ...
这帖子气氛真不怎么好 楼主提到了好莱坞原则,猜测是想应用Template Met ...
-- by bleakoasis -
如何减少子类对超类的依赖 ...
就模式说模式, 一点意义没有
-- by srdrm -
如何减少子类对超类的依赖 ...
这是非常经典的工厂方法模式. 为何会被这么多人唾弃, 很明显是LZ应用环境描述不 ...
-- by rappy -
如何减少子类对超类的依赖 ...
设计模式是很容易滥用的。继承也是。 楼主的问题的由来,就是滥用了继承。
-- by rtdb -
如何减少子类对超类的依赖 ...
我觉得还是尽量少用继承,多用组合或者聚合,毕竟继承的耦合度太高了。。
-- by lyxh_2003






评论排行榜