【譯】java8之接口的默認靜態方法

衆所周知,我們應該使用接口編程,接口使得在交互時不需要關注具體的實現細節,從而保持程序的鬆散耦合。在API的設計中,設計簡約而清晰的接口非常重要。被稱作固定定律的接口分離定律,其中有一條就講到了應該設計更小的特定客戶端接口而不是一個通用目的的接口。良好的接口設計是讓應用程序和庫的API保持簡潔高效的關鍵。

如果你曾有過接口API設計的經驗,那麼有時候你會感覺到爲API增加方法的必要。但是,如果API一旦發佈了,幾乎無法在保證已接口類的代碼不變的情況下來增加新的方法。舉個例子,假設你設計了一個簡單的API Calculator,裏面有add、subtract、devide和multiply函數。我們可以如下申明。簡便起見,我們使用int類型。

public interface Calculator {

  int add(int first, int second);

  int subtract(int first, int second);

  int divide(int number, int divisor);

  int multiply(int first, int second);
}

爲了實現Calculator這個接口,需要寫如下一個BasicCalculator類

public class BasicCalculator implements Calculator {

  @Override
  public int add(int first, int second) {
    return first + second;
  }

  @Override
  public int subtract(int first, int second) {
    return first - second;
  }

  @Override
  public int divide(int number, int divisor) {
    if (divisor == 0) {
      throw new IllegalArgumentException("divisor can't be           zero.");
    }
    return number / divisor;
  }

  @Override
  public int multiply(int first, int second) {
    return first * second;
  }
}

靜態工廠方法

乍一看,Calculator這個API還是非常簡單實用,其他開發者只需要創建一個BasicCalculator就可以使用這個API。比如下面這兩段代碼:

Calculator calculator = new BasicCalculator();
int sum = calculator.add(1, 2);

BasicCalculator cal = new BasicCalculator();
int difference = cal.subtract(3, 2);

然而,事實上給人的感覺卻是此API的用戶並不是面向這個接口進行編程,而是面向這個接口的實現類在編程。因爲你的API並沒有強制要求用戶把BasicCalculator當着一個public類來對接口進行編程,如果你把BasicCalculator類變成protected類型你就需要提供一個靜態的工廠類來謹慎的提供Calculator的實現,代碼如下:

class BasicCalculator implements Calculator {
  // rest remains same
}

接下來,寫一個工廠類來提供Calculator的實例,代碼如下:

public abstract class CalculatorFactory {

  public static Calculator getInstance() {
  return new BasicCalculator();
  }
}

這樣,用戶就被強制要求對Calculator接口進行編程,並且不需要關注接口的詳細實現。

儘管我們通過上面的方式達到了我們想要的效果,但是我們卻因增加了一個新的CalculaorFactory類而增加了API的表現層次。如果其他開發者要有效的利用這個Calculator API,就必須要先學會一個新的類的使用。但在java8之前,這是唯一的實現方式。

java8允許用戶在接口中定義靜態的方法,允許API的設計者在接口的內部定義類似getInstance的靜態工具方法,從而保持API簡潔。靜態的接口方法可以用來替代我們平時專爲某一類型寫的helper類。比如,Collections類是用來爲Collection和相關接口提供各種helper方法的一個類。Collections類中定義的方法應該直接添加到Collection及其它相關子接口上。

下面的代碼就是Java8中直接在Calculator接口中添加靜態getInstance方法的例證:

public interface Calculator {

  static Calculator getInstance() {
  return new BasicCalculator();
  }

  int add(int first, int second);

  int subtract(int first, int second);

  int divide(int number, int divisor);

  int multiply(int first, int second);

}

隨着時間來演進API

有些用戶打算通過新建一個Calculator子接口並在子接口中添加新方法或是通過重寫一個繼承自Calculator接口的實現類來增加類似remainder的方法。與他們溝通之後你會發現,他們可能僅僅是想向Calculator接口中添加一個類似remainder的方法。感覺這僅僅是一個非常簡單的API變化,所以你加入了一個方法,代碼如下:
public interface Calculator {

  static Calculator getInstance() {
    return new BasicCalculator();
  }

  int add(int first, int second);

  int subtract(int first, int second);

  int divide(int number, int divisor);

  int multiply(int first, int second);

  int remainder(int number, int divisor); // new method added to API
}

在接口中增加方法會破壞代碼的兼容性,這意味着那些實現Calculator接口的用戶需要爲remainder方法添加實現,否則,代碼無法編譯通過。這對API設計者來說非常麻煩,因爲它讓代碼演進變得非常困難。Java8之前,開發者不能直接在API中實現方法,這也使得在一個API中添加一個或者多個接口定義變得十分困難。

爲了讓API的演進變得更加容易,Java8允許用戶對定義在接口中的方法提供默認的實現。這就是通常的default或者defender方法。接口的實現類不需要再提供接口的實現方法。如果接口的實現類提供了方法,那麼實現類的方法會被調用,否則接口中的方法會被調用。List接口有幾個定義在接口中的默認方法,比如replaceAll,sort和splitIterator。

default void replaceAll(UnaryOperator<E> operator) {
  Objects.requireNonNull(operator);
  final ListIterator<E> li = this.listIterator();
  while (li.hasNext()) {
    li.set(operator.apply(li.next()));
  }
}

我們可以向下面的代碼一樣定義一個默認的方法來解決API的問題。默認方法通常用在使用那些已經存在的方法,比如remainder是用來定義使用subtract,multiply和divice的一個方法。

default int remainder(int number, int divisor) {
  return subtract(number, multiply(divisor, divide(number, divisor)));
}

多重繼承

一個類只能繼承自一個父類,但可以實現多個接口。既然在接口中實現方法是可行的,接口方法的多實現也是可行的。之前,Java已經在類型上支持多重繼承,如今也支持在表現階段的多重繼承。下面這三條規則決定了哪個方法會被調用。

規則一:定義在類中的方法優先於定義在接口的方法:

interface A {
  default void doSth(){
  System.out.println("inside A");
  }
}
class App implements A{

  @Override
  public void doSth() {
    System.out.println("inside App");
  }

  public static void main(String[] args) {
    new App().doSth();
  }
}

運行結果:inside App。調用的是在類中定義的方法。

規則二:否則,調用定製最深的接口中的方法。

interface A {
  default void doSth() {
    System.out.println("inside A");
  }
}
interface B {}
interface C extends A {
  default void doSth() {
    System.out.println("inside C");
  } 
}
class App implements C, B, A {

  public static void main(String[] args) {
    new App().doSth();
  }
}

運行結果:inside C

規則三:否則,直接調用指定接口的實現方法

interface A {
  default void doSth() {
    System.out.println("inside A");
  }
}
interface B {
  default void doSth() {
    System.out.println("inside B");
  }
}
class App implements B, A {

  @Override
  public void doSth() {
    B.super.doSth();
  }

  public static void main(String[] args) {
      new App().doSth();
  }
}

運行結果: inside B

水平有限,歡迎大家指點和建議,-

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