《Effective Java》阅读笔记18 接口优先于抽象类

1 Java程序提供两种机制来允许定义多个实现的类型:接口和抽象类。接口和抽象类区别在于抽象类可以包含某些方法的实现,但是接口却不允许

2 但最明显的区别还是在于若一旦继承于抽象类,那该类就是抽象类的子类,继承所带来的缺点也就随即而来,譬如Java只允许单继承

1. 接口优先于抽象类优点

1.1 <mark>已存在的类可以通过实现新接口来轻易的改造</mark>

接口类型相当于行为的抽象,对于一扇门来说打开(<mark>open()</mark>)、关闭(<mark>close()</mark>)是一组固有行为,如果你想新增加报警(<mark>alarm()</mark>)功能,则只需定义新的接口包含报警行为即可;而对于抽象类来说,在抽象类来添加<mark>alarm()</mark> 方法显然不适合,毕竟不是所有的门都必须包含此功能。

1.2 <mark>接口允许非层次类型框架的构建</mark>

类层次结构是指类与类之间的继承关系,对于组织某些事物来说是非常合适的,但并不是全部。如有个接口<mark>Singer</mark> ,有个接口<mark>SongWriter</mark> :

public interface Singer {
   
    //唱歌
    AudioClip sing(Song s);
}

public interface SongWriter {
   
    //作歌
    Song compose(boolean hit);
}

但是,对于有些歌手,他也会写词、作曲。那我们可以新定义接口<mark>SingerSongWriter</mark>:

public intreface SingerSongWriter extends Singer, SongWriter {
   
    //弹奏
    AudioClip strum();
    //激情表演
    void actSensitive();
}

试想一下,如果是抽象类作为类型的话,如:

Singer.java
public abstract class Singer {
   
    //唱歌
    public abstract AudioClip sing(Song s);
}

SongWriter.java
public abstract class SongWriter {
   
    //作歌
    public abstract Song compose(boolean hit);
}

SingerSongWriter.java
//在设计的时候感觉很不好设计。因为继承只能继承自一个类,那势必其他的方法需要重新声明
public abstract class SingerSongWriter extends SongWriter {
   
    //唱歌
    public abstract AudioClip sing(Song s);

    //弹奏
    public abstract AudioClip strum();
    //激情表演
    public abstract void actSensitive();
}

1.3 <mark>通过包装类型,接口可以使得安全地增强方法功能成为可能</mark>

2. Skeletal Implementation

现在地铁里有很多的自动售卖机。为了获取商品,通常都是激活屏幕、选择商品、支付然后拿到商品,然后结束。

首先我们采用接口的方式来实现:

public interface Ivending {
   
    void start();
    void chooseProduct();
    void stop();
    void process();
}

public class CandyVending implements Ivending {
   
    @Override
    public void start() {
   
        System.out.println("Start Vending machine");
    }

    @Override
    public void stop() {
   
        System.out.println("Stop Vending machine");
    }

    @Override
    public void process() {
   
        start();
        chooseProduct();
        stop();
    }

    @Override
    public void chooseProduct() {
   
      System.out.println("produce different candies");
      System.out.println("choose a type of candy");
      System.out.println("pay for candy");
      System.out.println("collect candy");
    }
}

public class DrinkVending  implements Ivending {
   
    @Override
    public void start() {
   
        System.out.println("Start Vending machine");
    }

    @Override
    public void stop() {
   
        System.out.println("Stop Vending machine");
    }

    @Override
    public void process() {
   
        start();
        chooseProduct();
        stop();
    }

    @Override
    public void chooseProduct() {
   
      System.out.println("produce different drinks");
      System.out.println("choose a type of drink");
      System.out.println("pay for the drink");
      System.out.println("collect the drink");
    }
}

public class VendingManager {
   
    public static void main(String[] args) {
   
        Ivending candy = new CandyVending();
        Ivending drink = new DrinkVending();
        candy.process();
        drink.process();
    }
}

采用接口方式的时候,大家有没有发现问题-重复了太多的代码。start()、stop()、process()都是重复的代码。

再用抽象类来实现:

public abstract class AbstractVending {
   
    public void start() {
   
        System.out.println("Start Vending machine");
    }

    public abstract void chooseProduct();

    public void stop() {
   
        System.out.println("Stop Vending machine");
    }

    void process() {
   
        start();
        chooseProduct();
        stop();
    }
}

public class CandyVending extends AbstractVending {
   
    @Override
    public void chooseProduct() {
   
      System.out.println("produce different candies");
      System.out.println("choose a type of candy");
      System.out.println("pay for candy");
      System.out.println("collect candy");
    }
}

public class DrinkVending extends AbstractVending {
   
    @Override
    public void chooseProduct() {
   
      System.out.println("produce different drinks");
      System.out.println("choose a type of drink");
      System.out.println("pay for the drink");
      System.out.println("collect the drink");
    }
}

public class VendingManager {
   
    public static void main(String[] args) {
   
        AbstractVending candy = new CandyVending();
        AbstractVending drink = new DrinkVending();
        candy.process();
        drink.process();
    }
}

相比于接口实现方式,没有重复代码。但是,这时候有个新要求,要求DrinkVending需要提供其他服务,譬如加热(warm())、冰镇(cold())。

public abstract class VendingService {
   
    public abstract void warm();
    public abstract void cold();
}

DrinkVending由于已经继承了AbstractVending,已不能再继承VendingService。

那么我们可以采用 Skeleton Implementation方法,其实现步骤如下:

  1. 创建接口;
  2. 为接口实现抽象类(AbstractInterfaceName),并在抽象类中实现重复的方法;
  3. 创建具体的类,并且新建继承自步骤2中产生的抽象类的内部类。那么这个具体的类就可以通过代理的方式调用抽象类中来自接口的方法及那些重复的方法

话不多说,代码如下:

public interface Ivending {
   
  void start();
  void chooseProduct();
  void stop();
  void process();
}

public class VendingService {
   
    public void warm() {
   
        System.out.println("加热");
    }

    public void cold() {
   
        System.out.println("冰镇");
    }
}

public abstract class AbstractVending implements Ivending {
   

  @Override
  public void start() {
   
    System.out.println("Start Vending machine");
  }

  @Override
  public void stop() {
   
    System.out.println("Stop Vending machine");
  }

  @Override
  public void process() {
   
    start();
    chooseProduct();
    stop();
  }
}

public class CandyVending implements Ivending {
   

  private class AbstractVendingDelegator extends AbstractVending {
   

    @Override
    public void chooseProduct() {
   
      System.out.println("produce different candies");
      System.out.println("choose a type of candy");
      System.out.println("pay for candy");
      System.out.println("collect candy");
    }
  }

  AbstractVendingDelegator delegator = new AbstractVendingDelegator();

  @Override
  public void start() {
   
    delegator.start();
  }

  @Override
  public void chooseProduct() {
   
    delegator.chooseProduct();
  }

  @Override
  public void stop() {
   
    delegator.stop();
  }

  @Override
  public void process() {
   
    delegator.process();
  }
}

public class DrinkVending extends VendingService implements Ivending {
   

  private class AbstractVendingDelegator extends AbstractVending {
   

    @Override
    public void chooseProduct() {
   
      System.out.println("produce different drinks");
      System.out.println("choose a type of drink");
      System.out.println("pay for the drink");
      System.out.println("collect the drink");
    }
  }

  AbstractVendingDelegator delegator = new AbstractVendingDelegator();

  @Override
  public void start() {
   
    delegator.start();
  }

  @Override
  public void chooseProduct() {
   
    delegator.chooseProduct();
  }

  @Override
  public void stop() {
   
    delegator.stop();
  }

  @Override
  public void process() {
   
    delegator.process();
  }
}

public class VendingManager {
   
    public static void main(String[] args) {
   
        Ivending candy = new CandyVending();
        Ivending drink = new DrinkVending();
        candy.process();
        System.out.println("*********************");
        drink.process();
        if(drink instanceof VendingService)
        {
   
            VendingService vs = (VendingService)drink;
            vs.service();
        }
    }
}

我们可以发现,这样解决了单独采用抽象类的问题-不能多继承实现类,也解决了采用接口方法中重复代码的问题。

像<mark>Java Collection Framework</mark> 中提供了大量的这种实现,大家有兴趣可以查看下<mark>AbstractCollection</mark> 、<mark>AbstractSet</mark> 、<mark>AbstractList</mark> 和<mark>AbstractMap</mark> 等。

3. 抽象类的使用

抽象类并不是一无是处。相比于接口来说,抽象类在版本迭代中,添加一个公共方法要简单的多。而接口需要在其实现类中都来实现一次。

总结
1 通常来说,对于多实现的方式,接口优先于抽象类。除非要求代码的改进演变要比灵活度更重要时,采用抽象类;
2 采用接口实现方式时,如若实现麻烦,参考<mark>Skeletal Implementation的</mark> 细节

4. 参考资料

深入理解Java的接口和抽象类