面向對象
我們常見的編程范式有命令式編程,函數式編程,邏輯式編程,而面向對象編程是一種命令式編程。
命令式編程是面向計算機硬件的一種抽象,有變量(存儲單元),賦值語句(獲取存儲指令),表達式(內存引用和算術運算)和控制語句(跳轉指令),命令式程序就是對一個馮諾依曼機的指令序列的抽象,面向對象是對我們現實世界模型的一個抽象,之后在映射到馮諾依曼機的指令序列。
面向對象的基本特性
如果只是用變量,賦值語句,表達式,控制語句去構建現實世界模型的話會非常困難,所以面向對象的出現的根本原因就是為了解決這個問題。
面向對象讓我們從指令代碼操作變量轉變為通過指令操作對象。 當我們理解了這個以后再去看面向對象的兩個特征:
抽象
封裝
首先抽象就是建立一個對現實模型。
封裝就是將變量,賦值語句,表達式,控制語句進行組合來描述上面的現實模型,并且將他們“打包”,看成一個原子結構,之后的程序邏輯都是圍繞著這個原子結構進行的。
其實面向對象的編程就是將原來的 ”模式一“ 改變為 ”模式二”
模式一:程序 = (賦值語句+表達式+控制語句)+ 變量
模式二:程序 = 對象 + 對象(對象之間的調用)
面向對象的高級特性
對象之間的關聯關系
抽象,封裝只是對現實模型和馮諾依曼機之間基本的映射關系,而現實世界中模型與模型之間還存在很多關系,如繼承、組合、依賴等。
而維護這些關系也成為面向對象語言的一個特性,并且有相應的語法支持。
1.繼承is-a組合:一個類繼承具有相似功能的另一個類,根據需要在所繼承的類基礎上進行擴展。
優點: 具有共同屬性和方法的類可以將共享信息抽象到父類中,增強代碼復用性,同時也是多態的基礎。
缺點: 子類中擴展的部分對父類不可見,另外如果共性比較少的時候使用繼承會增加冗余代碼。
2.組合has-a組合:has-a組合是在一個類中引用另一個類作為其成員變量。
優點: 可擴展性和靈活性高。在對象組合關系中應優先考慮has-a組合關系。
缺點: 具有共性的類之間看不到派生關系。
多態
多態在代碼復用中起著尤為重要的作用,假如對象A依賴對象B,如果有對象C繼承對象B,則說明對象C包含對象B(對象C的信息要多于對象B),所以當對象C向上轉型為對象B時不會出現信息的丟失,也就是說這種轉型是安全的。所以大部分靜態編程語言都支持向上轉型。當程序依賴對象B和程序依賴對象C(對象B的子類)時會有不同的表現,這就是多態。
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
以上是最簡單的多態的例子,通過上面例子很難發現多態的好處,這樣做的意義是什么?
觀察上面的代碼會發現,A的fun()方法參數為B,但是如果傳遞別的類型可不可以?答案是可以的,因為上面的代碼中我們傳遞了B的子類C,如果有D、E、F....都繼承B的話,也都可以傳遞到A的fun()中,所以起到了復用的作用。
但是還是體現不出來多態的好處,傳遞必須要繼承這個類,不符合現實模型,比如A是播放叫聲的裝置,B是斑點狗,所以將B傳遞進去時會播放斑點狗的叫聲,而C要是繼承B,在現實生活世界模型中,C也是斑點狗才行。
那如果我想播放牧羊犬的叫聲怎么辦?用抽象類就可以。如果B是抽象類的話,抽象的是狗,所以C可以任何狗,因為B中的所有東西都是抽象的,沒有任何描述狗細節的東西,所以可以是任何狗。
最后還是發現不完善,那如果我要播放貓叫聲怎么辦?這時候會有個比抽象類還抽象的東西,那就是接口,一個超級有用的家伙,有了他就可以完全和現實世界模型對應起來了,因為接口抽象的是行為,所以當B為接口時,規定B有叫聲方法,所以當A的fun方法中傳遞的B接口時,就可以理解為,只要傳遞實現叫聲方法的就可以,無論什么。所以當貓實現這個接口后,就可以被放到聲音播放器當中,同樣馬、豬、牛都可以,只要他們實現叫聲接口就行,但是樹可以吧?不可以,因為現實世界中的樹不可以叫。所以接口只抽象行為,完全可以實現現實模型的和面向對象的映射。
所以抽象程度從小到大分別為類>抽象類>接口。
- 當B為類時,A只能接收B子類。
- 當B為抽象類時,A能接收符合這個抽象的具體。
- 當B為接口時,A能接收實現B接口的任何東西,在當前上下文中就是有叫聲的任何東西。
所以當B為普通類時,A就是一個 斑點狗叫聲播放器。當B為抽象類時,A就為一個 狗的叫聲播放器。而當B為接口時,A就是一個 叫聲播放器,可以播放任何聲音,這就是多態的意義。
一個斑點狗播放器和一個可以播放任何聲音的播放器不存在那個更好,要根據實際的場景進行判斷,一個作用域小一個作用域大。
其實在框架中或者“造輪子的人”用接口的比較多,而“用輪子的人”用普通類比較多,所以當閱讀源碼發現各種接口時不要疑惑,那是多數是為了讓代碼通用、作用域大。