第18條:接口優于抽象類

java中可以用來定義允許多個實現的類型有兩種:接口和抽象類。

接口和抽象類的區別:

1,抽象類中可以存在某些方法的實現,接口不可以

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

3,java是單繼承,多實現

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

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

例如:我們要實現以下4種動物:

Dog - 狗狗;
Bat - 蝙蝠;
Parrot - 鸚鵡;
Ostrich - 鴕鳥。
如果按照哺乳動物和鳥類歸類,我們可以設計出這樣的類的層次:

animal? -? m????? b

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

animal-r???? f

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

哺乳類:能跑的哺乳類,能飛的哺乳類;
鳥類:能跑的鳥類,能飛的鳥類。
這么一來,類的層次就復雜了:

animal-mb-rf

如果要再增加“寵物類”和“非寵物類”,這么搞下去,類的數量會呈指數增長,很明顯這樣設計是不行的。

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

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
現在,我們要給動物再加上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
在設計類的繼承關系時,通常,主線都是單一繼承下來的,例如,Ostrich繼承自Bird。但是,如果需要“混入”額外的功能,通過多重繼承就可以實現,比如,讓Ostrich除了繼承自Bird外,再同時繼承Runnable。這種設計通常稱之為Mixin。

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

?接口允許我們構造非層次結構的類型框架。類型層次對于組織某些事物是非常合適的,但有些事物并不能被整齊地組織成一個嚴格的層次結構。例如,有一個接口代表singer,一個接口代表songwriter,這兩者之間可能并不存在父子關系,在現實中有些歌唱家本身也是作曲家。如果我們使用接口來定義這些類型,對于單個類而言,它同時實現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();??

}??

類似這種情況,如果咱們用創建層次類來實現的話,那么咱們每種情況都要創建一個類,就會導致組合臃腫


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

??????? 雖然接口不允許包含方法的實現,但是我們可以通過對導出的每個重要的接口都提供一個抽象的骨架實現類,這樣咱們可以把接口和抽象類的優點結合起來。接口的作用仍然是定義類型,骨架實現類接管了所有與接口實現相關的工作。按照慣例,骨架實現被稱為 AbstractInterface,這里的Interface是指所實現的接口的名子。如果設計得當,骨架實現可以使我們可以很容易提供自己的接口實現。例如,下面是一個靜態工廠方法,它包含一個完整的,功能全面的List實現。

//?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;??

????????}??

????};??

}??

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

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

public interface Mydemo{

public void c();

public void d();

public void f();?

};

2:定義一個抽象類,其中有兩個方法a和b,實現接口并實現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{//無需改變,繼承父類實現的方法,可以直接調用

public void a(){

}

public void b(){

}

//繼承AbrBase對IBase的實現

};

也可以直接用匿名內部類的方式轉發AbrMydemo中的方法,在maindemo中使用

public class maindemo {

public Mydemoa(){

Mydemo ?demo = new AbrMydemo()

demo.f();

}

public class Nbl(){

也可以在內部類中直接調用外部類的方法

}

public void b(){

}

//繼承AbrBase對IBase的實現

};

??????? 骨架實現的美妙之處在于,它們為抽象類提供了實現上的幫助,又不強加抽象類作為類型定義時的限制,可以擴展骨架實現也完全可以手動實現整個接口。此外骨架實現類也有助于接口的實現。實現了這個接口的類可以把對于接口方法的調用,轉發到另一個內部私有類的實例上,這個內部私有類擴展了骨架類的實現這種方法被稱為“模擬多重繼承”,這項技術具有多重繼承的絕大多數優點,同時又避開了相應的缺陷,實際上就是用包裝與轉發模擬了多個類的混合功能。


編寫骨架實現類相對比較簡單,但是有點單調乏味,首先,必須認真研究接口,并確定哪些方法是最為基本的,其他的方法則可以根據它們來實現。這些基本的方法將成為骨架實現類中的抽象方法。然后,必須為接口中所有其他的方法提供具體實現。例如接口Map與骨架實現AbstractMap。由于骨架實現是為了繼承的目的設計的,所以應該遵守第17條中介紹的所有關于設計和文檔的指導原則。

??????? 骨架實現上有個小小的不同,就是簡單實現。AbstractMap.SimpleEntry就是個例子。簡單實現就像個骨架實現,這是因為它實現了接口并且是為了繼承則設計的,但是區別在于它不是抽象的,它就最簡單的可能的有效實現,你可以原封不動地使用也可以看情況將它子類化。

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;??

????????}??

????}??


接口比抽象類優勢很多,但是抽象類仍然有個明顯的優勢:抽象類的演變比接口的演變要容易很多。如果在后續的版本中需要在抽象類中增加新的方法,那么建立一個具體方法后,就可以提供默認的實現,抽象類的所有實現都將提供這個新的方法。而接口,就不能這樣做。

雖然接口有骨架實現類,在接口中增加方法后,再骨架實現類也增加具體的方法不就可以解決了。可是那些不從骨架實現類繼承的接口實現仍然會遭到破壞。

因此,設計公有的接口要非常謹慎。接口一旦被公開發型,并且已被廣泛實現,再想改變這個接口幾乎是不可能的。在發行新接口的時候,最好的做法是,在接口被“凍結”之前,盡可能讓更多的程序員用盡可能多的方式來實現這個新街口,這樣有助于在依然可以改正缺陷的時候就發現它們。

簡言之,接口通常是定義允許多個實現的類型的最佳途徑。這條規則有個例外,既當演變的容易性比靈活性和功能更為重要的時候(這種情況,應該用抽象類來定義類型)。如果你導出了一個重要的接口,就應該堅決考慮同時提供骨架實現類。最后,應該盡可能謹慎地設計所有的公有接口,并通過編寫多個實現來對它們進行全面的測試

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,973評論 19 139
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,767評論 18 399
  • 1 場景問題# 1.1 發送提示消息## 考慮這樣一個實際的業務功能:發送提示消息。基本上所有帶業務流程處理的系統...
    七寸知架構閱讀 5,101評論 5 63
  • 蘇心2017閱讀 298評論 0 1
  • plan 完成原型2.0的設計 ,要點已經清楚 如果還有精力, 腹肌撕裂者 + nike training
    santiago_liii閱讀 133評論 0 0