Java 之路 (九) -- 接口(抽象類方法口四、接口孵运、多重繼承、接口與解耦等)

我們前面學(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ā)生什么励烦?

  1. 從面向?qū)ο笊侠斫猓撼橄箢愐饬x在于提供接口谓着,如果此時單獨創(chuàng)建抽象類,那它是幾乎沒什么意義的崩侠。
  2. 從安全性理解:抽象方法是不完整的漆魔,因此試圖產(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 特性

  1. 只是一種形式,本身不能做任何事情 -- 無法被實例化
  2. 實現(xiàn)類可以向上轉(zhuǎn)型為接口以现,以此實現(xiàn)類似”多重繼承“的特性
  3. 某類”實現(xiàn)“接口時狠怨,需要實現(xiàn)接口中全部的方法
  4. 接口中的方法默認為 public
  5. 接口中的域隱式地是 static 和 final 的
  6. 接口可以指明訪問權(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 多個接口老虫。

  1. 在接口中添加新方法
  2. 組合多個接口中的方法
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é)束乎完,共勉。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末品洛,一起剝皮案震驚了整個濱河市树姨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌桥状,老刑警劉巖帽揪,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異辅斟,居然都是意外死亡转晰,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門士飒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來查邢,“玉大人,你說我怎么就攤上這事酵幕∪排海” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵芳撒,是天一觀的道長邓深。 經(jīng)常有香客問我,道長笔刹,這世上最難降的妖魔是什么芥备? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮舌菜,結(jié)果婚禮上萌壳,老公的妹妹穿的比我還像新娘。我一直安慰自己酷师,他們只是感情好讶凉,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布染乌。 她就那樣靜靜地躺著山孔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪荷憋。 梳的紋絲不亂的頭發(fā)上台颠,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機與錄音,去河邊找鬼串前。 笑死瘫里,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的荡碾。 我是一名探鬼主播谨读,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼坛吁!你這毒婦竟也來了劳殖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤拨脉,失蹤者是張志新(化名)和其女友劉穎哆姻,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體玫膀,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡矛缨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了帖旨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片箕昭。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖碉就,靈堂內(nèi)的尸體忽然破棺而出盟广,到底是詐尸還是另有隱情,我是刑警寧澤瓮钥,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布筋量,位于F島的核電站,受9級特大地震影響碉熄,放射性物質(zhì)發(fā)生泄漏桨武。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一锈津、第九天 我趴在偏房一處隱蔽的房頂上張望呀酸。 院中可真熱鬧,春花似錦琼梆、人聲如沸性誉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽错览。三九已至,卻和暖如春煌往,著一層夾襖步出監(jiān)牢的瞬間倾哺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留羞海,地道東北人忌愚。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像却邓,于是被迫代替她去往敵國和親硕糊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理腊徙,服務(wù)發(fā)現(xiàn)癌幕,斷路器,智...
    卡卡羅2017閱讀 134,672評論 18 139
  • 一、抽象類 1时鸵、抽象類含義的概括: 當(dāng)多個類出現(xiàn)相同功能時胶逢,但功能主體不同,這樣可以向上抽取饰潜,抽取時只抽取功能定義...
    玉圣閱讀 834評論 0 6
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法初坠,類相關(guān)的語法,內(nèi)部類的語法彭雾,繼承相關(guān)的語法碟刺,異常的語法,線程的語...
    子非魚_t_閱讀 31,644評論 18 399
  • 抽象類 在繼承的層次結(jié)構(gòu)中者填,每個新子類都使類變得越來越明確具體。如果從一個子類追溯到父類做葵,類就會變得更通用和抽象占哟。...
    Steven1997閱讀 1,376評論 0 5
  • ——《朝花夕拾》讀后感 《朝花夕拾》是魯迅先生的一部文集。集中的文章寫于1962年酿矢,這時的作者已經(jīng)45歲榨乎,而文...
    寒秋閣閱讀 729評論 1 3