第18條:接口優(yōu)于抽象類

java中可以用來定義允許多個實現(xiàn)的類型有兩種:接口和抽象類鸳址。

接口和抽象類的區(qū)別:

1底桂,抽象類中可以存在某些方法的實現(xiàn)烹吵,接口不可以

2,如果要實現(xiàn)抽象類定義的類型详囤,類必須成為抽象類的子類袒啼。而對接口來說,任何一個類纬纪,只要實現(xiàn)接口里面必要的方法蚓再,就可以了,而且不用管這個類處于類的層次的哪個位置(例如:內(nèi)部類也可以實現(xiàn))

3包各,java是單繼承摘仅,多實現(xiàn)

現(xiàn)有的類可以很容易的被更新,以實現(xiàn)接口的形式问畅。例如娃属,現(xiàn)在有多個類要擴展排序功能,而咱們要做的就是對這些類實現(xiàn)comparable接口护姆,并且重新接口中必要的compare方法就可以了矾端。但是如果要用抽象類的形式擴展的話,那么就必須對抽象類所有的子類進行擴展卵皂。

接口是定義mixin(混合類型)的理想選擇秩铆。Comparable這樣的接口被稱為mixin的原因是:它允許任何的功能被混合到類型的主要功能中(排序功能)。抽象類并不能用于定義mixin灯变,原因在于它們不能被更新到現(xiàn)有類中:java是單繼承殴玛,多實現(xiàn)?所以類不可能有一個以上的父類,類層次結(jié)構(gòu)中也沒有合適的地方來插入mixin添祸,問題在于功能是可選的滚粟,如果把一個功能放入類層次中,那就將這項功能混入到了所有的子類中刃泌。

例如:我們要實現(xiàn)以下4種動物:

Dog - 狗狗凡壤;
Bat - 蝙蝠署尤;
Parrot - 鸚鵡;
Ostrich - 鴕鳥亚侠。
如果按照哺乳動物和鳥類歸類曹体,我們可以設(shè)計出這樣的類的層次:

animal? -? m????? b

但是如果按照“能跑”和“能飛”來歸類,我們就應(yīng)該設(shè)計出這樣的類的層次:

animal-r???? f

如果要把上面的兩種分類都包含進來盖奈,我們就得設(shè)計更多的層次:

哺乳類:能跑的哺乳類,能飛的哺乳類狐援;
鳥類:能跑的鳥類钢坦,能飛的鳥類。
這么一來啥酱,類的層次就復(fù)雜了:

animal-mb-rf

如果要再增加“寵物類”和“非寵物類”爹凹,這么搞下去,類的數(shù)量會呈指數(shù)增長镶殷,很明顯這樣設(shè)計是不行的禾酱。

正確的做法是采用多重繼承。首先绘趋,主要的類層次仍按照哺乳類和鳥類設(shè)計:

class Animal(object):
pass

抽象類:

class Mammal(Animal):
pass

class Bird(Animal):
pass

各種動物:

class Dog(Mammal):
pass

class Bat(Mammal):
pass

class Parrot(Bird):
pass

class Ostrich(Bird):
pass
現(xiàn)在颤陶,我們要給動物再加上Runnable和Flyable的功能,只需要先定義好Runnable和Flyable的類:

class Runnable(object):
def run(self):
print(‘Running…’)

class Flyable(object):
def fly(self):
print(‘Flying…’)
對于需要Runnable功能的動物陷遮,就多繼承一個Runnable滓走,例如Dog:
class Dog(Mammal, Runnable):
pass
對于需要Flyable功能的動物,就多繼承一個Flyable帽馋,例如Bat:

class Bat(Mammal, Flyable):
pass
通過多重繼承搅方,一個子類就可以同時獲得多個父類的所有功能。

Mixin
在設(shè)計類的繼承關(guān)系時绽族,通常姨涡,主線都是單一繼承下來的,例如吧慢,Ostrich繼承自Bird涛漂。但是,如果需要“混入”額外的功能检诗,通過多重繼承就可以實現(xiàn)怖喻,比如,讓Ostrich除了繼承自Bird外岁诉,再同時繼承Runnable锚沸。這種設(shè)計通常稱之為Mixin。

而此處如果咱們不用多繼承涕癣,那么咱們可以把跑和飛分別定義為一個接口哗蜈,咱們只要實現(xiàn)接口就可以了

?接口允許我們構(gòu)造非層次結(jié)構(gòu)的類型框架前标。類型層次對于組織某些事物是非常合適的,但有些事物并不能被整齊地組織成一個嚴(yán)格的層次結(jié)構(gòu)距潘。例如炼列,有一個接口代表singer,一個接口代表songwriter音比,這兩者之間可能并不存在父子關(guān)系俭尖,在現(xiàn)實中有些歌唱家本身也是作曲家。如果我們使用接口來定義這些類型洞翩,對于單個類而言稽犁,它同時實現(xiàn)Singer和songwriter是完成可以的,實際上骚亿,甚至可以定義第三個接口已亥,讓它同時擴展singer和songwriter,并且可以添加一些新的方法来屠。

public?interface?Singer?{??

????AudioClip?sing(Song?s);??

}??

public?interface?Songwriter?{??

????Song?compose(boolean?hit);??

}????

public?interface?SingerSongWriter?extends?Singer,?Songwriter?{??

????AudioClip?strum();??

????void?actSensitive();??

}??

類似這種情況虑椎,如果咱們用創(chuàng)建層次類來實現(xiàn)的話,那么咱們每種情況都要創(chuàng)建一個類俱笛,就會導(dǎo)致組合臃腫


第16條中介紹的包裝類模式捆姜,接口便利安全地增加類的功能成為可能。如果使用抽象類來定義類型迎膜,那么程序員除了使用繼承的手段來增加新的功能以外沒有其他的選擇娇未。這樣得到的類與包裝類相比,功能更差星虹,更脆弱零抬。

??????? 雖然接口不允許包含方法的實現(xiàn),但是我們可以通過對導(dǎo)出的每個重要的接口都提供一個抽象的骨架實現(xiàn)類宽涌,這樣咱們可以把接口和抽象類的優(yōu)點結(jié)合起來平夜。接口的作用仍然是定義類型,骨架實現(xiàn)類接管了所有與接口實現(xiàn)相關(guān)的工作卸亮。按照慣例忽妒,骨架實現(xiàn)被稱為 AbstractInterface,這里的Interface是指所實現(xiàn)的接口的名子兼贸。如果設(shè)計得當(dāng)段直,骨架實現(xiàn)可以使我們可以很容易提供自己的接口實現(xiàn)。例如溶诞,下面是一個靜態(tài)工廠方法鸯檬,它包含一個完整的,功能全面的List實現(xiàn)螺垢。

//?Concrete?implementation?built?atop?skeletal?implementation??

static?List<Integer>?intArrayAsList(final?int[]?a)?{??

????if?(a?==?null)??

????????throw?new?NullPointerException();??

????return?new?AbstractList<Integer>()?{??

????????public?Integer?get(int?i)?{??

????????????return?a[i];??

????????}??

????????@Override??

????????public?Integer?set(int?i,?Integer?val)?{??

????????????int?oldVal?=?a[i];??

????????????a[i]?=?val;??

????????????return?oldVal;??

????????}??

????????public?int?size()?{??

????????????return?a.length;??

????????}??

????};??

}??

??????? 上面的代碼中利用了骨架實現(xiàn)AbstractList來實現(xiàn)一個完整的List喧务,實際上赖歌,骨架是做為一個被匿名內(nèi)部類的擴展類存在的,在這個內(nèi)部類中對骨架實現(xiàn)新增加了兩個方法并重寫了一個方法以定制我們所需要的List的功能功茴。由于骨架實現(xiàn)本身已經(jīng)實現(xiàn)了很多通用的操作庐冯,在這里實際上只做了很少的改動就得到了一個功能良好的類。

1 :定義一個基本的接口坎穿,其中有c 展父,d,f 三種方法

public interface Mydemo{

public void c();

public void d();

public void f();?

};

2:定義一個抽象類玲昧,其中有兩個方法a和b栖茉,實現(xiàn)接口并實現(xiàn)c和d和 f

public abstract class AbrMydemo implements Mydemo{

public void a();

public void b();

public void c(){

}

public void d(){

}

public void f(){?

}

};

public class maindemo extends AbrMydemo{//無需改變,繼承父類實現(xiàn)的方法酌呆,可以直接調(diào)用

public void a(){

}

public void b(){

}

//繼承AbrBase對IBase的實現(xiàn)

};

也可以直接用匿名內(nèi)部類的方式轉(zhuǎn)發(fā)AbrMydemo中的方法衡载,在maindemo中使用

public class maindemo {

public Mydemoa(){

Mydemo ?demo = new AbrMydemo()

demo.f();

}

public class Nbl(){

也可以在內(nèi)部類中直接調(diào)用外部類的方法

}

public void b(){

}

//繼承AbrBase對IBase的實現(xiàn)

};

??????? 骨架實現(xiàn)的美妙之處在于搔耕,它們?yōu)槌橄箢愄峁┝藢崿F(xiàn)上的幫助隙袁,又不強加抽象類作為類型定義時的限制,可以擴展骨架實現(xiàn)也完全可以手動實現(xiàn)整個接口弃榨。此外骨架實現(xiàn)類也有助于接口的實現(xiàn)菩收。實現(xiàn)了這個接口的類可以把對于接口方法的調(diào)用,轉(zhuǎn)發(fā)到另一個內(nèi)部私有類的實例上鲸睛,這個內(nèi)部私有類擴展了骨架類的實現(xiàn)這種方法被稱為“模擬多重繼承”娜饵,這項技術(shù)具有多重繼承的絕大多數(shù)優(yōu)點,同時又避開了相應(yīng)的缺陷官辈,實際上就是用包裝與轉(zhuǎn)發(fā)模擬了多個類的混合功能箱舞。


編寫骨架實現(xiàn)類相對比較簡單,但是有點單調(diào)乏味拳亿,首先晴股,必須認(rèn)真研究接口,并確定哪些方法是最為基本的肺魁,其他的方法則可以根據(jù)它們來實現(xiàn)电湘。這些基本的方法將成為骨架實現(xiàn)類中的抽象方法。然后鹅经,必須為接口中所有其他的方法提供具體實現(xiàn)寂呛。例如接口Map與骨架實現(xiàn)AbstractMap。由于骨架實現(xiàn)是為了繼承的目的設(shè)計的瘾晃,所以應(yīng)該遵守第17條中介紹的所有關(guān)于設(shè)計和文檔的指導(dǎo)原則贷痪。

??????? 骨架實現(xiàn)上有個小小的不同,就是簡單實現(xiàn)蹦误。AbstractMap.SimpleEntry就是個例子呢诬。簡單實現(xiàn)就像個骨架實現(xiàn)涌哲,這是因為它實現(xiàn)了接口并且是為了繼承則設(shè)計的,但是區(qū)別在于它不是抽象的尚镰,它就最簡單的可能的有效實現(xiàn)阀圾,你可以原封不動地使用也可以看情況將它子類化。

public?static?class?SimpleEntry<K,V>??implements?Entry<K,V>,?java.io.Serializable??

????{??

????????private?static?final?long?serialVersionUID?=?-8499721149061103585L;??

????????private?final?K?key;??

????????private?V?value;??

????????public?SimpleEntry(K?key,?V?value)?{??

????????????this.key???=?key;??

????????????this.value?=?value;??

????????}??

????????public?SimpleEntry(Entry<??extends?K,???extends?V>?entry)?{??

????????????this.key???=?entry.getKey();??

????????????this.value?=?entry.getValue();??

????????}??

????????public?K?getKey()?{??

????????????return?key;??

????????}??

????????public?V?getValue()?{??

????????????return?value;??

????????}??

????????public?V?setValue(V?value)?{??

????????????V?oldValue?=?this.value;??

????????????this.value?=?value;??

????????????return?oldValue;??

????????}??

????????public?boolean?equals(Object?o)?{??

????????????if?(!(o?instanceof?Map.Entry))??

????????????????return?false;??

????????????Map.Entry<?,?>?e?=?(Map.Entry<?,?>)o;??

????????????return?eq(key,?e.getKey())?&&?eq(value,?e.getValue());??

????????}??

????????public?int?hashCode()?{??

????????????return?(key???==?null???0?:???key.hashCode())?^??

???????????????????(value?==?null???0?:?value.hashCode());??

????????}??

????????public?String?toString()?{??

????????????return?key?+?"="?+?value;??

????????}??

????}??


接口比抽象類優(yōu)勢很多狗唉,但是抽象類仍然有個明顯的優(yōu)勢:抽象類的演變比接口的演變要容易很多初烘。如果在后續(xù)的版本中需要在抽象類中增加新的方法,那么建立一個具體方法后分俯,就可以提供默認(rèn)的實現(xiàn)肾筐,抽象類的所有實現(xiàn)都將提供這個新的方法。而接口缸剪,就不能這樣做吗铐。

雖然接口有骨架實現(xiàn)類,在接口中增加方法后杏节,再骨架實現(xiàn)類也增加具體的方法不就可以解決了唬渗。可是那些不從骨架實現(xiàn)類繼承的接口實現(xiàn)仍然會遭到破壞奋渔。

因此镊逝,設(shè)計公有的接口要非常謹(jǐn)慎。接口一旦被公開發(fā)型嫉鲸,并且已被廣泛實現(xiàn)撑蒜,再想改變這個接口幾乎是不可能的。在發(fā)行新接口的時候玄渗,最好的做法是座菠,在接口被“凍結(jié)”之前,盡可能讓更多的程序員用盡可能多的方式來實現(xiàn)這個新街口藤树,這樣有助于在依然可以改正缺陷的時候就發(fā)現(xiàn)它們浴滴。

簡言之,接口通常是定義允許多個實現(xiàn)的類型的最佳途徑也榄。這條規(guī)則有個例外巡莹,既當(dāng)演變的容易性比靈活性和功能更為重要的時候(這種情況,應(yīng)該用抽象類來定義類型)甜紫。如果你導(dǎo)出了一個重要的接口降宅,就應(yīng)該堅決考慮同時提供骨架實現(xiàn)類。最后囚霸,應(yīng)該盡可能謹(jǐn)慎地設(shè)計所有的公有接口腰根,并通過編寫多個實現(xiàn)來對它們進行全面的測試

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拓型,一起剝皮案震驚了整個濱河市额嘿,隨后出現(xiàn)的幾起案子瘸恼,更是在濱河造成了極大的恐慌,老刑警劉巖册养,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件东帅,死亡現(xiàn)場離奇詭異,居然都是意外死亡球拦,警方通過查閱死者的電腦和手機靠闭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坎炼,“玉大人愧膀,你說我怎么就攤上這事∫ス猓” “怎么了檩淋?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長萄金。 經(jīng)常有香客問我蟀悦,道長,這世上最難降的妖魔是什么捡絮? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任熬芜,我火速辦了婚禮莲镣,結(jié)果婚禮上福稳,老公的妹妹穿的比我還像新娘。我一直安慰自己瑞侮,他們只是感情好的圆,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著半火,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上棕兼,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天枢析,我揣著相機與錄音,去河邊找鬼店归。 笑死阎抒,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的消痛。 我是一名探鬼主播且叁,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼秩伞!你這毒婦竟也來了逞带?” 一聲冷哼從身側(cè)響起欺矫,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎展氓,沒想到半個月后穆趴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡遇汞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年毡代,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勺疼。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡教寂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出执庐,到底是詐尸還是另有隱情酪耕,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布轨淌,位于F島的核電站迂烁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏递鹉。R本人自食惡果不足惜盟步,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望躏结。 院中可真熱鬧却盘,春花似錦、人聲如沸媳拴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽屈溉。三九已至塞关,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間子巾,已是汗流浹背帆赢。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留线梗,地道東北人椰于。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像缠导,于是被迫代替她去往敵國和親廉羔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)憋他,斷路器孩饼,智...
    卡卡羅2017閱讀 134,659評論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法竹挡,內(nèi)部類的語法镀娶,繼承相關(guān)的語法,異常的語法揪罕,線程的語...
    子非魚_t_閱讀 31,639評論 18 399
  • 1 場景問題# 1.1 發(fā)送提示消息## 考慮這樣一個實際的業(yè)務(wù)功能:發(fā)送提示消息梯码。基本上所有帶業(yè)務(wù)流程處理的系統(tǒng)...
    七寸知架構(gòu)閱讀 5,009評論 5 63
  • 蘇心2017閱讀 290評論 0 1
  • plan 完成原型2.0的設(shè)計 ,要點已經(jīng)清楚 如果還有精力, 腹肌撕裂者 + nike training
    santiago_liii閱讀 115評論 0 0