【Effective Java】條18:接口優先於抽象類

Java程序提供兩種機制來允許定義多個實現的類型:接口和抽象類。接口和抽象類區別1在於抽象類可以包含某些方法的實現,但是接口卻不允許;但最明顯的區別還是在於若一旦繼承於抽象類,那該類就是抽象類的子類,繼承所帶來的缺點也就隨即而來,譬如Java只允許單繼承。

接口優先於抽象類優點

  1. 已存在的類可以通過實現新接口來輕易的改造

    接口類型相當於行爲的抽象,對於一扇門來說打開(open())、關閉(close())是一組固有行爲,如果你想新增加報警(alarm())功能,則只需定義新的接口包含報警行爲即可;而對於抽象類來說,在抽象類來添加alarm()方法顯然不適合,畢竟不是所有的門都必須包含此功能。

  2. 接口允許非層次類型框架的構建

    類層次結構2是指類與類之間的繼承關係,對於組織某些事物來說是非常合適的,但並不是全部。如有個接口Singer,有個接口SongWriter

    public interface Singer {
        //唱歌
        AudioClip sing(Song s);
    }
    
    public interface SongWriter {
        //作歌
        Song compose(boolean hit);
    }

    但是,對於有些歌手,他也會寫詞、作曲。那我們可以新定義接口SingerSongWriter

    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();
    }
  3. 通過包裝類型,接口可以使得安全地增強方法功能成爲可能。可以參見《【Effective Java】條16:複合優於繼承》

Skeletal Implementation3

現在地鐵裏有很多的自動售賣機。爲了獲取商品,通常都是激活屏幕、選擇商品、支付然後拿到商品,然後結束。

首先我們採用接口的方式來實現:

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();
        }
    }
}

我們可以發現,這樣解決了單獨採用抽象類的問題-不能多繼承實現類,也解決了採用接口方法中重複代碼的問題。

Java Collection Framework中提供了大量的這種實現,大家有興趣可以查看下AbstractCollectionAbstractSetAbstractListAbstractMap等。

抽象類的使用

抽象類並不是一無是處。相比於接口來說,抽象類在版本迭代中,添加一個公共方法要簡單的多。而接口需要在其實現類中都來實現一次。

總結

  1. 通常來說,對於多實現的方式,接口優先於抽象類。除非要求代碼的改進演變要比靈活度更重要時,採用抽象類;
  2. 採用接口實現方式時,如若實現麻煩,參考Skeletal Implementation的細節

參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章