- 原文作者: shekhargulati
- 譯文出自: leege100
- 譯者: leege100
- 狀態(tài): 完成
眾所周知视卢,我們應該使用接口編程,接口使得在交互時不需要關注具體的實現(xiàn)細節(jié)瓤漏,從而保持程序的松散耦合腾夯。在API的設計中,設計簡約而清晰的接口非常重要蔬充。被稱作固定定律的接口分離定律,其中有一條就講到了應該設計更小的特定客戶端接口而不是一個通用目的的接口班利。良好的接口設計是讓應用程序和庫的API保持簡潔高效的關鍵饥漫。
如果你曾有過接口API設計的經(jīng)驗,那么有時候你會感覺到為API增加方法的必要罗标。但是庸队,如果API一旦發(fā)布了积蜻,幾乎無法在保證已接口類的代碼不變的情況下來增加新的方法。舉個例子彻消,假設你設計了一個簡單的API Calculator竿拆,里面有add、subtract宾尚、devide和multiply函數(shù)丙笋。我們可以如下申明。簡便起見煌贴,我們使用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);
}
為了實現(xiàn)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;
}
}
靜態(tài)工廠方法
乍一看牛郑,Calculator這個API還是非常簡單實用巩掺,其他開發(fā)者只需要創(chuàng)建一個BasicCalculator就可以使用這個API沮榜。比如下面這兩段代碼:
Calculator calculator = new BasicCalculator();
int sum = calculator.add(1, 2);
BasicCalculator cal = new BasicCalculator();
int difference = cal.subtract(3, 2);
然而,事實上給人的感覺卻是此API的用戶并不是面向這個接口進行編程,而是面向這個接口的實現(xiàn)類在編程冲呢。因為你的API并沒有強制要求用戶把BasicCalculator當著一個public類來對接口進行編程,如果你把BasicCalculator類變成protected類型你就需要提供一個靜態(tài)的工廠類來謹慎的提供Calculator的實現(xiàn)低矮,代碼如下:
class BasicCalculator implements Calculator {
// rest remains same
}
接下來油宜,寫一個工廠類來提供Calculator的實例,代碼如下:
public abstract class CalculatorFactory {
public static Calculator getInstance() {
return new BasicCalculator();
}
}
這樣者甲,用戶就被強制要求對Calculator接口進行編程春感,并且不需要關注接口的詳細實現(xiàn)。
盡管我們通過上面的方式達到了我們想要的效果虏缸,但是我們卻因增加了一個新的CalculaorFactory類而增加了API的表現(xiàn)層次鲫懒。如果其他開發(fā)者要有效的利用這個Calculator API,就必須要先學會一個新的類的使用刽辙。但在java8之前窥岩,這是唯一的實現(xiàn)方式。
java8允許用戶在接口中定義靜態(tài)的方法宰缤,允許API的設計者在接口的內(nèi)部定義類似getInstance的靜態(tài)工具方法颂翼,從而保持API簡潔。靜態(tài)的接口方法可以用來替代我們平時專為某一類型寫的helper類慨灭。比如朦乏,Collections類是用來為Collection和相關接口提供各種helper方法的一個類。Collections類中定義的方法應該直接添加到Collection及其它相關子接口上氧骤。
下面的代碼就是Java8中直接在Calculator接口中添加靜態(tài)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接口的實現(xiàn)類來增加類似remainder的方法呻疹。與他們溝通之后你會發(fā)現(xiàn),他們可能僅僅是想向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
}
在接口中增加方法會破壞代碼的兼容性,這意味著那些實現(xiàn)Calculator接口的用戶需要為remainder方法添加實現(xiàn)并思,否則庐氮,代碼無法編譯通過。這對API設計者來說非常麻煩宋彼,因為它讓代碼演進變得非常困難弄砍。Java8之前,開發(fā)者不能直接在API中實現(xiàn)方法宙暇,這也使得在一個API中添加一個或者多個接口定義變得十分困難输枯。
為了讓API的演進變得更加容易,Java8允許用戶對定義在接口中的方法提供默認的實現(xiàn)占贫。這就是通常的default或者defender方法桃熄。接口的實現(xiàn)類不需要再提供接口的實現(xiàn)方法。如果接口的實現(xiàn)類提供了方法型奥,那么實現(xiàn)類的方法會被調(diào)用瞳收,否則接口中的方法會被調(diào)用。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的問題。默認方法通常用在使用那些已經(jīng)存在的方法烫葬,比如remainder是用來定義使用subtract,multiply和divice的一個方法界弧。
default int remainder(int number, int divisor) {
return subtract(number, multiply(divisor, divide(number, divisor)));
}
多重繼承
一個類只能繼承自一個父類,但可以實現(xiàn)多個接口搭综。既然在接口中實現(xiàn)方法是可行的垢箕,接口方法的多實現(xiàn)也是可行的。之前兑巾,Java已經(jīng)在類型上支持多重繼承条获,如今也支持在表現(xiàn)階段的多重繼承。下面這三條規(guī)則決定了哪個方法會被調(diào)用蒋歌。
規(guī)則一:定義在類中的方法優(yōu)先于定義在接口的方法:
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帅掘。調(diào)用的是在類中定義的方法。
規(guī)則二:否則堂油,調(diào)用定制最深的接口中的方法修档。
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
規(guī)則三:否則,直接調(diào)用指定接口的實現(xiàn)方法
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
水平有限府框,歡迎大家指點和建議萍悴,-