我們前面學(xué)過通過“隱藏實現(xiàn)”可以將接口與實現(xiàn)分離蔓彩,然而它僅僅作為基礎(chǔ)治笨,而本章的接口以及下一章的內(nèi)部類 為我們提供了一種將接口和實現(xiàn)分離的更加結(jié)構(gòu)化的方法。
話不多說赤嚼,進入正題旷赖。
1. 抽象類和抽象方法
抽象類是普通的類與接口之間的一種中庸之道。
1.1 什么是抽象類/方法
抽象方法
- 定義:指的是一些只有方法聲明更卒,而沒有具體方法體的方法等孵。
- 聲明語法:通過 abstract 關(guān)鍵字,如
abstract void method();
抽象類:
- 定義:包含抽象方法的類叫做抽象類蹂空。
- 聲明方法:通過 abstract 關(guān)鍵字流济,如
abstract class Instrument { ... }
- 要點:
- 如果一個類包含一個或多個抽象方法,該類必須被限定為 抽象的( abstract )腌闯。
- 抽象類中可以有 非抽象方法,
- 抽象類可以沒有抽象方法雕憔。
- 抽象類不能被實例化
1.2 為什么需要抽象類/方法
1.2.1 抽象類/方法 的意義
回顧上一張關(guān)于"樂器"(instrument) 的例子姿骏,會發(fā)現(xiàn)這樣一個有趣的現(xiàn)象:基類 Instrument 中的方法全部是“啞”方法,如果我們需要表達一個特定行為斤彼,就必須使用導(dǎo)出類中的方法分瘦。
這里啞方法個人理解是該方法沒有實際意義,只是提供接口供子類覆蓋琉苇。
雖然在上一章是作為 多態(tài) 的例子引入嘲玫,但是從設(shè)計的角度上考慮,Instrument 類的目的是為它的所有導(dǎo)出類創(chuàng)建一個 通用接口并扇。
建立該接口的唯一理由:不同的子類可以用不同的形式表示此接口去团。
通用接口建立起一種基本形式,以此表示所有導(dǎo)出類的共同方法穷蛹,換句話說就是將 Instrument 類稱作抽象基類土陪。
于是我們總結(jié)出抽象類的目的:
-
通過通用接口操縱一系列類,該抽象類只是提供了一系列接口肴熏,其中沒有具體的實現(xiàn)內(nèi)容
抽象類/方法使類的抽象性明確鬼雀,并告訴用戶和編譯器打算如何使用它們。
同時抽象類也是有用的重構(gòu)工具蛙吏,因為它們使得我們可以很容易的將公共方法沿著繼承層次結(jié)構(gòu)向上移動源哩。
1.2.2 抽象類不能被實例化
上面我們分析了抽象類的意義鞋吉,但是又出現(xiàn)了問題:假如我單獨創(chuàng)建一個抽象類的對象,會發(fā)生什么励烦?
- 從面向?qū)ο笊侠斫猓撼橄箢愐饬x在于提供接口谓着,如果此時單獨創(chuàng)建抽象類,那它是幾乎沒什么意義的崩侠。
- 從安全性理解:抽象方法是不完整的漆魔,因此試圖產(chǎn)生抽象類的對象是 不安全 的。
出于以上兩點考慮却音,編譯器會確保我們不會誤用抽象類:當(dāng)我們試圖產(chǎn)生抽象類的對象時改抡,編譯器會提示錯誤消息。
因此我們得出結(jié)論:
- 抽象類不能被實例化
結(jié)論得出了系瓢,但是還沒完阿纤。。夷陋。
1.2.3 繼承抽象類
某天你可能定義了一個抽象類欠拾,然后派生出了導(dǎo)出類 A,這時你心想:A 只需要表現(xiàn)某方面的特性骗绕,并不需要抽象類中的所有特性藐窄,于是只覆蓋了基類的部分抽象方法。嗯酬土,然后你就拿著 A 去創(chuàng)建對象了荆忍。
然后你就會驚訝的發(fā)現(xiàn):程序出錯了!為什么會這樣呢撤缴?
事實上刹枉,如果抽象類的派生類中有任何一個抽象方法未定義,那么該導(dǎo)出類就同樣是抽象類屈呕。編譯器會強制我們用 abstract 關(guān)鍵字來指明該導(dǎo)出類是抽象類微宝。
1.2.4 沒有抽象方法的抽象類
抽象類可以沒有抽象方法,這個特性記住吧虎眨,原書并未給出任何解釋蟋软。
本節(jié)主要是介紹一下 沒有抽象方法的抽象類,意義何在嗽桩?
-
價值在于:不需要實例化钟鸵,也不需要通過不同的對象來保存不同的狀態(tài)。
因為該抽象類已經(jīng)把方法都實現(xiàn)了涤躲。
使用場景:用在各種 工具類 中棺耍,如果它的所有方法都是靜態(tài)的,那么定義為抽象的种樱,就會避免我們實例化這個工具類蒙袍。
2. 接口
接口 (interface) 使抽象的概念更進一步俊卤。
2.1 什么是接口(interface)
簡單來說,接口(interface 關(guān)鍵字) 是一個完全抽象的類害幅,它是抽象方法的集合消恍。
2.1.1 特性
- 只是一種形式,本身不能做任何事情 -- 無法被實例化
- 實現(xiàn)類可以向上轉(zhuǎn)型為接口以现,以此實現(xiàn)類似”多重繼承“的特性
- 某類”實現(xiàn)“接口時狠怨,需要實現(xiàn)接口中全部的方法
- 接口中的方法默認為 public
- 接口中的域隱式地是 static 和 final 的
- 接口可以指明訪問權(quán)限,類似 class
2.1.2 用途
被用來建立類與類之間的協(xié)議
任何使用某接口的代碼都知道且僅需知道可以調(diào)用該接口的哪些方法邑遏。
2.1.3 語法
接口的創(chuàng)建 -- interface 關(guān)鍵字
[權(quán)限修飾詞] interface 接口名{
//...
//聲明抽象方法
}
接口的實現(xiàn) -- implements 關(guān)鍵字
[權(quán)限修飾詞] class 類名 implements 接口名{
//...
//實現(xiàn)接口中的全部方法
}
2.2 接口與完全解耦
此處原文中通過三個例子循序漸進的介紹如何提高代碼復(fù)用性佣赖,逐步加深解耦程度。
2.2.1 例1 -- 父子類 & 向上轉(zhuǎn)型 & 策略模式
代碼如下:
class Processor{
public String name(){
return getClass().getSimpleName();
}
//子類中重寫次此方法時用其他類型如string int 等
Object process(Object input){
return input;
}
}
class Upcase extends Processor{
String process(Object input){
return ((String)input).toUpperCase();
}
}
class Downcase extends Processor{
String process(Object input){
return ((String)input).toLowerCase();
}
}
class Splitter extends Processor{
String process(Object input){
return Arrays.toString(((String)input).split(" "));
}
}
public class Apply{
public static void process(Processor p,Object s){
System.out.println("Using Processor"+p.name());
System.out.println(p.process(s));
}
public static String s="this is a Sup--Sub Coupling";
public static void main(String[] args){
process(new Upcase(),s);
process(new Downcase(),s);
process(new Splitter(),s);
}
}
//輸出結(jié)果:
/*
Using ProcessorUpcase
THIS IS A SUP--SUB COUPLING
Using ProcessorDowncase
this is a sup--sub coupling
Using ProcessorSplitter
[this, is, a, Sup--Sub, Coupling]
*/
基類 Processor 中有一個 name() 方法记盒,另外有一個 process() 方法憎蛤,該方法根據(jù)接受不同輸入?yún)?shù),修改值后產(chǎn)生輸出纪吮。接下來對基類進行擴展俩檬,派生出不中類型的 Processor。
Apply.process() 方法接收任何類型的 Processor碾盟,并將其應(yīng)用到 Object 對象上棚辽,然后打印結(jié)果。這里其實用到了策略設(shè)計模式冰肴,策略就是傳遞進去的參數(shù)對象屈藐,它包含了要執(zhí)行的代碼。
策略設(shè)計模式:創(chuàng)建一個根據(jù)所傳遞的參數(shù)對象的不同而具有不同行為的方法嚼沿。
核心就是"封裝變化":方法包含所要執(zhí)行的算法中固定不變的部分,"策略"包含變化的部分
此時瓷患,假如我們發(fā)現(xiàn)了另一組類(電子濾波器)如下:
class Waveform{
private static long counter;
private final long id=counter++;
public String toString(){
return "Waveform:"+id;
}
}
class Filter{
public String name(){
return getClass().getSimpleName();
}
public Waveform process(Waveform input){
return input;
}
}
class UpFilter extends Filter{
double cutoff;
UpFilter(double cutoff){ this.cutoff = curoff;}
Waveform process(Waveform input){
return input;
}
}
//...
能看到骡尽,F(xiàn)ilter 和 Processor 具有相同的接口元素,但是由于二者并非繼承關(guān)系擅编,因此 Apply.process() 方法不能傳入 Filter 參數(shù)(Apply.process() 方法和 Processor 緊緊綁在一起)攀细,使得不能復(fù)用Apply.process() 的代碼。
問題:
- Apply.process() 和 Processor 耦合度太高爱态。
2.2.2 例2 -- 定義接口
在上面的問題下谭贪,我們將 Processor 定義為接口,然后復(fù)用該接口的 Apply.process()锦担。
代碼如下:
public interface Processor{
String name();
Object process(Object input);
}
public class Apply {
public static void process(Processor p,Object s){
System.out.println("Using Processor"+p.name());
System.out.println(p.process(s));
}
}
這樣一來俭识,如果我們可以修改電子濾波器 Filter 類的話,我們只需要讓其實現(xiàn)此接口(public abstract class Filter implements Processor
)洞渔,然后再派生出不同子類即可套媚。
2.2.3 例3 -- 對接口進行適配
但是缚态,當(dāng)我們無法修改 Filter 類的時候,我們就需要使用適配器模式堤瘤,接收擁有的接口玫芦,并以此產(chǎn)生需要的接口。
如下:
class FilterAdapter implements Processor{
Filter filter;
public FilterAdapter(Filter filter){
this.filter=filter;
}
public String name(){return filter.name();}
public Waveform process(Object input){
return filter.process((Waveform)input);
}
}
上面的三種處理方式是循序漸進的本辐,將接口從具體實現(xiàn)中解耦使得接口可以應(yīng)用于多種不同的具體實現(xiàn)桥帆,因此代碼也就更具復(fù)用性。
2.3 接口的擴展 -- 通過繼承
接口可以繼承接口慎皱,且一個接口可以同時 extends 多個接口老虫。
- 在接口中添加新方法
- 組合多個接口中的方法
interface A { void methodA(); }
interface B { void methodB(); }
// 在 A 接口的基礎(chǔ)上,添加新方法
interface C extends A { void methodC(); }
// 組合 A 和 B 接口宝冕,同時添加了新方法
interface D extends A,B { void methodD(); }
class Test1 implements C {
void methodA(){ /*..具體實現(xiàn)*/ }
void methodC(){ /*..具體實現(xiàn)*/ }
}
class Test2 implements D {
void methodA(){ /*..具體實現(xiàn)*/ }
void methodB(){ /*..具體實現(xiàn)*/ }
void methodD(){ /*..具體實現(xiàn)*/ }
}
需要注意:
- 多重繼承時张遭,方法名最好不要重復(fù)。
當(dāng)方法名相同地梨,而參數(shù)列表或者返回類型不同時菊卷,會報錯。
2.4 接口的其他事項
接口與多重繼承
- 一個實現(xiàn)類可以同時實現(xiàn)多個接口
-
實現(xiàn)類可以向上轉(zhuǎn)型為接口
多重繼承指 C++ 中的概念:組合多個類的接口的行為宝剖。
多重繼承并非一個類同時繼承多個類洁闰,不要混淆。
接口中的域:
- 兩點:
- 接口中的任何域都自動是 static 和 final 的
- 接口中的域自動是public 的 万细。
- 初始化
- 接口中定義的域不能是 空final扑眉,但可以被非常量表達式初始化。
- 在類第一次被加載時初始化赖钞,發(fā)生在任何域首次被訪問時腰素。
接口的適配:
- 主要就是適配器模式,見 2.2.3 例3.
接口與工廠:
- 設(shè)計模式 - 工廠方法雪营。
接口的嵌套
- 嵌套接口:定義在類或接口中的接口弓千。
- 修飾符限制:
- 不論定義在接口,還是類中献起,嵌套接口默認強制是
static
洋访。這意味著,嵌套接口是沒有局部的嵌套接口谴餐。 - 接口定義在類中姻政,可以使用四種訪問權(quán)限,定義在接口種岂嗓,則只有
public
- 不論定義在接口,還是類中献起,嵌套接口默認強制是
總結(jié)
任何抽象性都應(yīng)該是應(yīng)需求而產(chǎn)生的汁展。必需時應(yīng)該重構(gòu)接口,而不是到處添加額外的類。
恰當(dāng)?shù)脑O(shè)計原則是優(yōu)先選擇類而不是接口善镰。從類開始妹萨,如果接口變得必需,那么就進行重構(gòu)炫欺。
本章結(jié)束乎完,共勉。