继承是代码复用的最佳方案吗?

继承是代码复用的最佳方案吗?

继承,一个父类可有许多个子类。父类就是把一些公共代码放进去,之后在实现其他子类时,少写一些代码。

代码复用,很多人觉得继承就是绝佳方案。若把继承理解成代码复用,更多是站在子类角度向上看。在客户端代码使用时,面对的是子类,这种继承叫实现继承:

Child object = new Child();

还有一种看待继承的角度:从父类往下看,客户端使用时,面对的是父类,这种继承叫接口继承:

Parent object = new Child();

但接口继承更多和多态相关。本文主要讨论实现继承。

不推荐实现继承

  • 继承很宝贵,Java只支持单继承 一个类只能有一个父类,一旦继承的位置被实现继承占据,再想做接口继承就难了
  • 实现继承通常也是一种受程序设计语言局限的思维方式 很多语言,不使用继承,也有代码复用方案

1 案例

产品报表服务,其中的某服务:查询产品信息。该查询过程通用,别的服务也可用。所以,我把它放父类以复用:

class BaseService {
  // 获取相应产品信息
  protected List<Product> getProducts(List<String> product) {
// 生成报表服务
class ReportService extends BaseService {
  public void report() {
    List<Product> product = getProduct(...);
    // 生成报表
}

ReportService没有继承任何类,但也可复用代码,即ProductFetcher模块。 这样,若我需要有个获取产品信息的地方,它不必非得是个服务,我无需继承任何类。

获取产品信息、生成报表是两件事,只是因为在生成报表过程,需要获取产品信息,所以,它有个基类。

不用继承的实现:

class ProductFetcher {
  // 获取相应的产品信息
  public List<Product> getProducts(List<String> product) {
// 生成报表服务
class ReportService {
  private ProductFetcher fetcher;
  public void report() {
    List<Product> product = fetcher.getProducts(...);
    // 生成报表
}

这就是组合:ReportService里组合一个ProductFetcher。 设计通用原则:组合优于继承。即若一个方案既能用组合实现,也能用继承实现,那就用组合。

所以,要写继承以实现代码复用时,问问自己,这是接口继承,还是实现继承? 若是实现继承,是不是可以写成组合?

2 面向组合编程

可以组合的根因:获取产品信息、生成报表服务本是两件事(分离关注点)。 你要是看出是两件事了,就不会把它们放一起。

分解是设计的第一步,分解粒度越小越好。当可分解出多个关注点,每个关注点就是个独立类。最终类由这一个个小类组合而得,即面向组合编程。按面向组合思维:为增加复杂度,增加一个报表生成器(ReportGenerator),在获取产品信息后,生成报表:

class ReportService {
  private ProductFetcher fetcher;
  private ReportGenerator generator;