抽象类通常代表一个抽象概念,它提供一个继承的出发点。而具体类则不同,具体类可以实例化,应当给出一个有商业逻辑实现的对象模版。由于抽象类不可以实例化,因此一个设计师设计一个新的抽象类,一定是用来继承的。
而这一个声明倒过来也是对的:具体类不是用来继承的。
具体类不是用来继承的
只要有可能,不要从具体类继承。
如下图所示,此类图就给出了一个继承形成的等级结构的典型例子。可以看出,所有的继承都是从抽象类开始的,而所有的具体类都没有子类。
换言之,在一个以继承关系形成的等级结构里面,树叶节点均应当是具体类,而树枝节点均应当是抽象类(或者Java接口)。
这样的设计是所有的Java设计师都应当努力做到的。
代码重构的建议
如果在一个原始的设计里,有两个具体类之间有继承关系,那么最可能的修改方案是怎样呢?
假设有两个具体类,类A和类B,类B是类A的子类,那么一个最简单的修改方案应当是建立一个抽象类(或者Java接口)C,然后让类A和类B成为抽象类C的子类,如下图所示。
上面所给出的重构的例子实际上具有更加广泛的意义,这就是里氏替换原则。
抽象类应当拥有尽可能多的共同代码
在一个从抽象类到多个具体类的继承关系中,共同的代码应当尽量移动到抽象类里。
在一个继承的等级结构中,共同的代码应当尽量向等级结构的上方移动,如下图所示。把重复的代码从子类里面移动到超类里面,可以提高代码的复用率。由于代码在共同的超类而不是几个子类中出现,在代码发生改变时,设计师只需要修改一个地方。这对代码复用明显是有利的。
一个对象从超类继承而来的代码,在不使用时不会造成对资源的浪费。回到前面所讨论的代码重构的例子,设计师将类A和类B的共享代码尽量移动到抽象超类C里面,是说尽可能将公共的方法移动到抽象策略角色中,如下图所示:
抽象类应当拥有尽可能少的数据
与代码的移动方向相反的是,数据的移动方向是从抽象类到具体类,也即从继承的等级结构的高端想等级结构的低端移动,如下图所示。一个对象的数据不论是否使用都会占用资源,因此数据应当尽量放到具体类或者等级结构的低端。
回到前面所讨论的重构的例子,设计师应当将类A和类B的数据保持在各自的类中,而不是移动到抽象超类C里面,这样可以保证节省内存资源。