因为我曾经坚信“组合优于继承”,“只要implement,不要extend”。写这篇文章,某种意义上算是对我曾经有偏见的继承/多继承道个歉,以后我会将它们加入常规武器中。
我和很多面向对象初学者一样,都经历过滥用继承的阶段。我刚开始学习面向对象,使用的是C++,开发时最主要是使用类来划分程序模块。因为用继承来扩展一个类十分方便而直观,所以我和很多面向对象的初学者一样十分喜欢使用继承。但是滥用继承会使得代码逻辑和类阶层不必要的复杂和冗余,最后让开发者自食其果。这也是导致继承名声变臭的原因。
有一定开发经验后,我开始学习设计模式,顿时觉得豁然开朗,解决了很多疑惑。于是我把《设计模式》一书奉为
圭璧,将里面的每句话当做
金科玉律。由于以前吃过继承的亏,加上从书本、论坛上看到大家包括一些权威人士对“继承”的抨击。所以我认定:extend是邪恶的
。在代码中看到如果父类子类间没有kindof关系,就觉得浑身不自在。即使有kindof关系,也总是想用抽个Strategy之类的方式改成组合+implement。
现在想想觉得有点矫枉过正过分设计了。诚然,总是能将extend替换成组合+implement。但是除了满足美学方面的追求(假定继承是丑陋的),很多时候并没有带来什么好处,代码倒是多了好些,而且很多是毫无营养的“一句流”:比如《设计模式》里的代理模式,如果大部分函数不用改变,这些函数体都是一句话。我不敢说“一句流”是坏味道,怕被喷死,因为很多框架中广泛存在一句流。但从我的开发经验来看:1)从某种角度看也是种冗余,如果函数参数改变/改名/删除,需要联动修改;2)调试麻烦;3)带来程序阅读上的难度,真正干活的类有时很难知道在哪里。有点讽刺吧?设计模式承诺带来灵活易理解且可复用的代码,但可能起到反效果。
组合vs继承,前者更灵活强大,避免了继承的强耦合关系,但后者也有自己优势。我现在的看法是,使用组合还是继承,要依情况而定,而不是光靠“面向对象第X定律”。就举代理模式为例,如果大部分方法需要有额外处理,那使用代理模式无可厚非。但如果只是少数方法要改变,写100个void foo(){proxy.foo();}有意思吗?程序员何苦难为自己。直接用继承,改掉要修改的少数方法得了。至于扩展性和可维护性,根据我的经验,往往代码量和源码文件越少,越容易修改,而不是取决于代码里用了多少设计模式。
我的建议:一般情况下使用组合,但如果使用组合会导致大量没营养的单句函数的话,预示着更适用继承——除非有多个实现类,或者显然将有多个实现类。组合未必优于继承,过度设计和滥用继承,是在这两者间权衡不当导致的后果。
Java开发者看到上面就可以了。C++开发者可以继续往下看。C++有个比继承名声更臭的语法:多重继承。多重继承有几个问题:菱形缺陷(可采用虚拟继承来规避)、方法名冲突(这个好解决)和this跳转(小心即可,影响不大)。以前我对多重继承也是退避三舍。但最近发现如果要把多个模块组成杀人武器霸王2000。用多重继承很方便的。当然,使用多重继承就不能替换模块的具体实现了。