面向?qū)ο?/h3>
我們常見(jiàn)的編程范式有命令式編程,函數(shù)式編程,邏輯式編程,而面向?qū)ο缶幊淌且环N命令式編程。
命令式編程是面向計(jì)算機(jī)硬件的一種抽象,有變量(存儲(chǔ)單元),賦值語(yǔ)句(獲取存儲(chǔ)指令),表達(dá)式(內(nèi)存引用和算術(shù)運(yùn)算)和控制語(yǔ)句(跳轉(zhuǎn)指令),命令式程序就是對(duì)一個(gè)馮諾依曼機(jī)的指令序列的抽象,面向?qū)ο笫菍?duì)我們現(xiàn)實(shí)世界模型的一個(gè)抽象,之后在映射到馮諾依曼機(jī)的指令序列。
面向?qū)ο蟮幕咎匦?/h4>
如果只是用變量,賦值語(yǔ)句,表達(dá)式,控制語(yǔ)句去構(gòu)建現(xiàn)實(shí)世界模型的話會(huì)非常困難,所以面向?qū)ο蟮某霈F(xiàn)的根本原因就是為了解決這個(gè)問(wèn)題。
面向?qū)ο笞屛覀儚闹噶畲a操作變量轉(zhuǎn)變?yōu)橥ㄟ^(guò)指令操作對(duì)象。 當(dāng)我們理解了這個(gè)以后再去看面向?qū)ο蟮膬蓚€(gè)特征:
抽象
封裝
首先抽象就是建立一個(gè)對(duì)現(xiàn)實(shí)模型。
封裝就是將變量,賦值語(yǔ)句,表達(dá)式,控制語(yǔ)句進(jìn)行組合來(lái)描述上面的現(xiàn)實(shí)模型,并且將他們“打包”,看成一個(gè)原子結(jié)構(gòu),之后的程序邏輯都是圍繞著這個(gè)原子結(jié)構(gòu)進(jìn)行的。
其實(shí)面向?qū)ο蟮木幊叹褪菍⒃瓉?lái)的 ”模式一“ 改變?yōu)?”模式二”
模式一:程序 = (賦值語(yǔ)句+表達(dá)式+控制語(yǔ)句)+ 變量
模式二:程序 = 對(duì)象 + 對(duì)象(對(duì)象之間的調(diào)用)
面向?qū)ο蟮母呒?jí)特性
對(duì)象之間的關(guān)聯(lián)關(guān)系
抽象,封裝只是對(duì)現(xiàn)實(shí)模型和馮諾依曼機(jī)之間基本的映射關(guān)系,而現(xiàn)實(shí)世界中模型與模型之間還存在很多關(guān)系,如繼承、組合、依賴等。
而維護(hù)這些關(guān)系也成為面向?qū)ο笳Z(yǔ)言的一個(gè)特性,并且有相應(yīng)的語(yǔ)法支持。
1.繼承is-a組合:一個(gè)類(lèi)繼承具有相似功能的另一個(gè)類(lèi),根據(jù)需要在所繼承的類(lèi)基礎(chǔ)上進(jìn)行擴(kuò)展。
優(yōu)點(diǎn): 具有共同屬性和方法的類(lèi)可以將共享信息抽象到父類(lèi)中,增強(qiáng)代碼復(fù)用性,同時(shí)也是多態(tài)的基礎(chǔ)。
缺點(diǎn): 子類(lèi)中擴(kuò)展的部分對(duì)父類(lèi)不可見(jiàn),另外如果共性比較少的時(shí)候使用繼承會(huì)增加冗余代碼。
2.組合has-a組合:has-a組合是在一個(gè)類(lèi)中引用另一個(gè)類(lèi)作為其成員變量。
優(yōu)點(diǎn): 可擴(kuò)展性和靈活性高。在對(duì)象組合關(guān)系中應(yīng)優(yōu)先考慮has-a組合關(guān)系。
缺點(diǎn): 具有共性的類(lèi)之間看不到派生關(guān)系。
多態(tài)
多態(tài)在代碼復(fù)用中起著尤為重要的作用,假如對(duì)象A依賴對(duì)象B,如果有對(duì)象C繼承對(duì)象B,則說(shuō)明對(duì)象C包含對(duì)象B(對(duì)象C的信息要多于對(duì)象B),所以當(dāng)對(duì)象C向上轉(zhuǎn)型為對(duì)象B時(shí)不會(huì)出現(xiàn)信息的丟失,也就是說(shuō)這種轉(zhuǎn)型是安全的。所以大部分靜態(tài)編程語(yǔ)言都支持向上轉(zhuǎn)型。當(dāng)程序依賴對(duì)象B和程序依賴對(duì)象C(對(duì)象B的子類(lèi))時(shí)會(huì)有不同的表現(xiàn),這就是多態(tài)。
public class A {
public void fun(B b){
b.fun();
}
}
public class B {
public void fun(){
System.out.println("我是b");
}
}
public class C extends B{
//重載
public void fun(){
System.out.println("我是c");
}
}
public class Test {
public static void main(String[] args) {
A a = new A();
B b = new B();
C c = new C();
a.fun ( b );
a.fun ( c );
}
}
-------------------------------------------------
Output:
我是b
我是c
以上是最簡(jiǎn)單的多態(tài)的例子,通過(guò)上面例子很難發(fā)現(xiàn)多態(tài)的好處,這樣做的意義是什么?
觀察上面的代碼會(huì)發(fā)現(xiàn),A的fun()方法參數(shù)為B,但是如果傳遞別的類(lèi)型可不可以?答案是可以的,因?yàn)樯厦娴拇a中我們傳遞了B的子類(lèi)C,如果有D、E、F....都繼承B的話,也都可以傳遞到A的fun()中,所以起到了復(fù)用的作用。
但是還是體現(xiàn)不出來(lái)多態(tài)的好處,傳遞必須要繼承這個(gè)類(lèi),不符合現(xiàn)實(shí)模型,比如A是播放叫聲的裝置,B是斑點(diǎn)狗,所以將B傳遞進(jìn)去時(shí)會(huì)播放斑點(diǎn)狗的叫聲,而C要是繼承B,在現(xiàn)實(shí)生活世界模型中,C也是斑點(diǎn)狗才行。
那如果我想播放牧羊犬的叫聲怎么辦?用抽象類(lèi)就可以。如果B是抽象類(lèi)的話,抽象的是狗,所以C可以任何狗,因?yàn)锽中的所有東西都是抽象的,沒(méi)有任何描述狗細(xì)節(jié)的東西,所以可以是任何狗。
最后還是發(fā)現(xiàn)不完善,那如果我要播放貓叫聲怎么辦?這時(shí)候會(huì)有個(gè)比抽象類(lèi)還抽象的東西,那就是接口,一個(gè)超級(jí)有用的家伙,有了他就可以完全和現(xiàn)實(shí)世界模型對(duì)應(yīng)起來(lái)了,因?yàn)榻涌诔橄蟮氖切袨?,所以?dāng)B為接口時(shí),規(guī)定B有叫聲方法,所以當(dāng)A的fun方法中傳遞的B接口時(shí),就可以理解為,只要傳遞實(shí)現(xiàn)叫聲方法的就可以,無(wú)論什么。所以當(dāng)貓實(shí)現(xiàn)這個(gè)接口后,就可以被放到聲音播放器當(dāng)中,同樣馬、豬、牛都可以,只要他們實(shí)現(xiàn)叫聲接口就行,但是樹(shù)可以吧?不可以,因?yàn)楝F(xiàn)實(shí)世界中的樹(shù)不可以叫。所以接口只抽象行為,完全可以實(shí)現(xiàn)現(xiàn)實(shí)模型的和面向?qū)ο蟮挠成洹?/p>
所以抽象程度從小到大分別為類(lèi)>抽象類(lèi)>接口。
- 當(dāng)B為類(lèi)時(shí),A只能接收B子類(lèi)。
- 當(dāng)B為抽象類(lèi)時(shí),A能接收符合這個(gè)抽象的具體。
- 當(dāng)B為接口時(shí),A能接收實(shí)現(xiàn)B接口的任何東西,在當(dāng)前上下文中就是有叫聲的任何東西。
所以當(dāng)B為普通類(lèi)時(shí),A就是一個(gè) 斑點(diǎn)狗叫聲播放器。當(dāng)B為抽象類(lèi)時(shí),A就為一個(gè) 狗的叫聲播放器。而當(dāng)B為接口時(shí),A就是一個(gè) 叫聲播放器,可以播放任何聲音,這就是多態(tài)的意義。
一個(gè)斑點(diǎn)狗播放器和一個(gè)可以播放任何聲音的播放器不存在那個(gè)更好,要根據(jù)實(shí)際的場(chǎng)景進(jìn)行判斷,一個(gè)作用域小一個(gè)作用域大。
其實(shí)在框架中或者“造輪子的人”用接口的比較多,而“用輪子的人”用普通類(lèi)比較多,所以當(dāng)閱讀源碼發(fā)現(xiàn)各種接口時(shí)不要疑惑,那是多數(shù)是為了讓代碼通用、作用域大。