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)來對它們進行全面的測試。