在程序設計過程中,讀者很可能遇到這樣一種困境:設計了一個接口,但實現這個接口的子類並不需要實現接口中的全部方法,也就是說,接口中的方法過多,對於某些子類是多余的,我們不得不浪費的寫上一個空的實現。
今天小菜提到的“抽象接口”,就是用來解決這個問題的。
為了不誤導讀者,先說明一下,什么是“抽象接口”。
所謂“抽象接口”,即在提供接口的同時,提供一個抽象類,用抽象類實現該接口(實際上這是缺省適配模式)。
下面小菜舉個例子,讓讀者體會這樣做的好處。
代碼寫的不咋地,為了防止讀者看不懂,先上一張類圖:
具體代碼:
ITestInterface.java
1 /* 2 假設有一個頂層接口 3 */ 4 public interface ITestInterface{ 5 void method1(); 6 int method2(); 7 boolean method3(); 8 }
TestAbstract.java
1 /* 2 抽象類abstract實現了ITestInterface頂層接口 3 */ 4 5 public abstract class TestAbstract implements ITestInterface{ 6 //找出接口中必要的方法,也就是子類必須實現的方法,定義成抽象方法,交由子類實現 7 public abstract void method1(); 8 public abstract int method2(); 9 10 //一些獨特的方法可以在抽象類中默認實現 11 public boolean method3(){ 12 return true; 13 } 14 }
TestClass1.java
1 /* 2 普通類TestClass1繼承了TestAbstract抽象類 3 */ 4 5 public class TestClass1 extends TestAbstract{ 6 7 //TestClass1必須實現抽象的method1方法,該方法最早是接口中定義的 8 public void method1(){ 9 10 } 11 //TestClass1必須實現抽象的method2方法,該方法最早是接口中定義的 12 public int method2(){ 13 return 1; 14 } 15 16 //接口中的method3方法對於TestClass1無關緊要,因此不做重寫。 17 }
TestClass2.java
1 /* 2 普通類TestClass2繼承了TestAbstract抽象類 3 */ 4 5 public class TestClass2 extends TestAbstract{ 6 7 //TestClass2必須實現抽象的method1方法,該方法最早是接口中定義的 8 public void method1(){ 9 10 } 11 //TestClass2必須實現抽象的method2方法,該方法最早是接口中定義的 12 public int method2(){ 13 return 2; 14 } 15 16 //method3方法對於TestClass2來說至關重要,因此必須重寫。 17 public boolean method3(){ 18 return false; 19 } 20 21 }
代碼精講:
從以上例子可以看出,最高層的接口被一個抽象類實現,在抽象類中,我們把關鍵的method1、method2方法定義成抽象方法,強制子類去實現,而“獨特”的method3方法在抽象類中做一個默認實現。
等到TestClass1、TestClass2繼承TestAbstract抽象類時,優勢就體現出來了,TestClass1、TestClass2必須實現method1、method2,但如果用不到method3,可以直接無視。
通過接口和抽象類的結合,避免了在實現接口的子類中出現大量的“無意義”實現,這個“無意義”實現,被緩沖到了抽象類中,完美展現了代碼復用(可以把抽象類理解成接口和實現類之間的緩沖)。
需要指出的是,我們既可以選擇繼承抽象類,也可以選擇實現接口,並不是說一定要繼承抽象類,看情況而定,這里是兩種選擇,兩個機會。
寫到這,或許讀者覺得文章已經結束了,其實沒有。。。
這樣做的好處不僅僅是這一點,細細品味,假如我們向接口中增加了一個方法。。。
具體代碼:
溫馨提示:不要被代碼嚇到,其實這些代碼和上邊的差不多,只不過加了個方法而已。
ITestInterface.java
1 /* 2 假設有一個頂層接口 3 */ 4 public interface ITestInterface{ 5 void method1(); 6 int method2(); 7 boolean method3(); 8 //接口中新增加了方法 9 String method4(); 10 }
TestAbstract.java
1 /* 2 抽象類abstract實現了ITestInterface頂層接口 3 */ 4 5 public abstract class TestAbstract implements ITestInterface{ 6 //找出接口中必要的方法,也就是子類必須實現的方法,定義成抽象方法,交由子類實現 7 public abstract void method1(); 8 public abstract int method2(); 9 10 //一些獨特的方法可以在抽象類中默認實現 11 public boolean method3(){ 12 return true; 13 } 14 15 //抽象類中提供一個默認實現,這樣就可以避免"驚動"所有子類 16 public String method4(){ 17 return ""; 18 } 19 }
TestClass1.java
1 /* 2 普通類TestClass1繼承了TestAbstract抽象類 3 */ 4 5 public class TestClass1 extends TestAbstract{ 6 7 //TestClass1必須實現抽象的method1方法,該方法最早是接口中定義的 8 public void method1(){ 9 10 } 11 //TestClass1必須實現抽象的method2方法,該方法最早是接口中定義的 12 public int method2(){ 13 return 1; 14 } 15 16 //接口中的method3方法對於TestClass1無關緊要,因此不做重寫。 17 18 //新增的方法對於TestClass1來說至關重要,因此必須重寫 19 public String method4(){ 20 return "Class1"; 21 } 22 23 }
TestClass2.java
1 /* 2 普通類TestClass2繼承了TestAbstract抽象類 3 */ 4 5 public class TestClass2 extends TestAbstract{ 6 7 //TestClass2必須實現抽象的method1方法,該方法最早是接口中定義的 8 public void method1(){ 9 10 } 11 //TestClass2必須實現抽象的method2方法,該方法最早是接口中定義的 12 public int method2(){ 13 return 2; 14 } 15 16 //method3方法對於TestClass2來說至關重要,因此必須重寫。 17 public boolean method3(){ 18 return false; 19 } 20 21 //新增的方法對於TestClass2來說無關緊要,無需知道新增method4的存在 22 }
代碼精講:
這段代碼演示了假如項目已經成型,但是需求有變,我們不得不向接口中增加一個新的方法,假如子類直接實現了接口,那么這些子類都要修改,來實現接口新增的方法。
但本例中的TestClass1、TestClass2子類沒有直接實現接口,而是通過繼承抽象類間接實現接口,這樣好處一下就體現出來了!
向接口中新增的方法,可以在實現接口的抽象類中緩沖一下,提供一個默認的實現,這樣一來,就不必強制所有的子類(通過繼承抽象類間接實現接口的類)都進行修改,可以形象的理解為“沒有驚動子類”。而需要使用這個方法的子類,直接重寫即可。
小菜感慨:
人類的智慧真偉大!數組和鏈表結合,產生了高效的哈希表;接口和抽象類結合,產生了優雅的缺省適配模式。大家努力吧!!!
寫在后面的話:
世間沒有完美的事物,設計模式也是如此,過多的討論優缺點沒有意義,合適的就是最好的,什么是合適的呢?這才是體現智慧的地方。