封裝繼承多態這三個概念不是C++特有的,而是所有OOP具有的特性。
由於C++語言支持這三個特性,所以學習C++時不可避免的要理解這些概念。
而在大部分C++教材中這些概念是作為鋪墊,接下來就花大部分篇幅講語言機制如如何實現這些特性的。
其實以上三個概念是不是OOP編程語言具有的特性,而是現實世界本身所具有的規律,只是OOP提煉了這些特性而已。
而且技術面試時最喜歡問的就是虛函數,而要理解虛函數這三個概念一個都少不了。
下面來分別描述這三個概念
【封裝】
以下偷懶引自百度百科的描述。
隱藏對象的屬性和實現細節,僅對外公開接口,控制在程序中屬性的讀和修改的訪問級別;將抽象得到的數據和行為(或功能)相結合,形成一個有機的整體,也就是將數據與操作數據的源代碼進行有機的結合,形成“類”,其中數據和函數都是類的成員。
我的描述:封裝在這里的解釋我意為特化到編程語言上的解釋,還原來理解,世間萬物都是封裝呈現給我們的。
比方說有一家銀行,銀行對我們而言就是一個封裝好的概念,我們不需要知道資金是如何流轉的,不需要知道錢是怎么取出來的,但銀行對外開放提供貸款,存款等公用服務,我們又能實實在在的取到錢,通過銀行我們能獲得想要的服務。
提煉的結果就是:我們而不需要知道銀行(封裝模型)是如何運作的,只需要知道提供哪些銀行卡/賬單(封裝模型的外部輸入)等,就能獲得哪些服務(封裝對外開放的接口)。
再擴大一點,任何一個事物,電腦、手機、小貓小狗、公車、國家、首腦、地球、車展、晚會、酒吧等等,我們都可以從封裝的角度去理解。
以上事物都可以提煉出提供哪些服務(需求決定的)的概念,並且我們不知道電腦/手機/小貓小狗這些事物的具體構造。
於是封裝的概念應該是這樣子的:封裝是一個抽象的模型,該模型對外提供服務,而任何使用該模型的用戶不需要知道模型是如何運作的。
我們還原了封裝的本來的面貌:世間萬物本身具有的特性。
那么來思考這樣的一個問題,如果讓你來設計一門編程語言,你要用編程語言來描述這一特性,你要怎么設計。
你會得到以下三個結論:
1.封裝模型要包含固有屬性(數據)---銀行有多少員工,金庫有多少錢等等
2.封裝模型要提供服務(操作)---存款,貸款等
3.要使用訪問權限來控制對外開放和隱藏---櫃台窗口,金庫保險箱密碼等
這樣甚至能得到面向過程編程語言到面向對象語言的發展是必然的這一結論。
映射到C++中就成了類,於是C++的類的描述可以寫成這樣:類是一個抽象的模型,該模型對外提供接口,而使用該類的用戶不需要知道類是如何實現的。
上面的三個結論在C++中依次為:
1.類的數據成員-----封裝模型的固有屬性(數據)
2.類的成員函數-----封裝模型提供的服務(操作)
3.訪問權限關鍵字---使用訪問權限來控制對外開放和隱藏
C++語言只是將封裝這一概念從世間萬物的本質特性提煉了出來,而C++語言設計者必然要設計出支持這一特性的語言機制,因為只有這樣才能把現實生活中的事物映射到編程語言給實現出來。
至於各種教材上寫道的封裝的目的是是增強安全性和簡化編程,我覺得就是扯淡,就是語言機制必須這樣設計,不這么設計你幾乎不可能建軟件模型(架構)。
【繼承】
以下同樣引自百度百科的描述。
繼承是面向對象語言的重要機制。借助繼承,可以擴展原有的代碼,應用到其他程序中,而不必重新編寫這些代碼。在java語言中,繼承是通過擴展原有的類,聲明新類來實現的。擴展聲明的新類稱為子類,原有的類稱為超類(父類)。繼承機制規定,子類可以擁有超類的所有屬性和方法,也可以擴展定義自己特有的屬性,增加新方法和重新定義超類的方法。
沿用解釋【封裝】的思路,我們依然可以還原繼承的本質----世間萬物的本質特性(更抽象一點的概念)。
為什么說是更抽象一點的概念?
如果封裝的概念只去關注個體,那么繼承就是去關注個體與個體之間的關系。封裝我們只關注了一個模型,現在多個模型要產生關系,對這種關系要提煉一下。
相比封裝而言,個體與個體間的關系,當然是關系更抽象一點了,那么把此關系還原到世間萬物的本質特性就需要甄別一番。
最顯而易見的是生物學對物種的分類,是非常好的關系模型。
先分一個物種大類,然后細分,再細分。生物學分七次:種、屬、科、目、綱、門、界。
生物學分好了,物理學是不是也分好了:物理學-力學-流體力學。
再來看看文學:小說-科幻小說-硬科幻小說。
我的描述:所謂繼承就是層次分類,並且世間萬物絕大部分都是可以層次分類的。
那么分類的依據是什么?也就是根據層次分類要設計哪些語言機制。
為了描述方便我們引入 上某層和下某層 的概念
1.提取個體模型之間的共性-第一次分類得到結果N1---相對N2是下一層(個體之間的比較關系用相對這個詞)
2.在N1這一層繼續提取共性-第二次分類得到結果N2---相對N1是上一層
...
3.在Nn這一層提取共性-得到分類結果Nn---相對N1是上n層
用一張圖來表示:
用Nn加入一些特有屬性可能會變成Nn-1,N5甚至N1。
那么語言機制的特性必然是:
1.上某層必然包含下某層的屬性和操作----子類包含父類的數據成員和成員函數
2.上某層加入某些某些屬性和操作會變成下某層----使用繼承加入數據成員和成員函數
這個加入的過程就是繼承。
因為每次提煉都會舍棄很多個體的特有屬性,所以分的層次越多,到最后的分類結果Nn可能是非常抽象的----我們又可以得到一些邊邊角角的結論,抽象基類的概念。
我們還可以解釋為什么抽象基類實例化會很奇怪,因為沒有人會這么說:我有生物學。而你卻可以說我有小狗。
繼承只是分類過程中發現的一種關系,當然還有組合關系,其他關系。
設計有一條原則是:優先使用組合,而不是繼承,這也證明了世間萬物的組合關系的數目是遠大於繼承關系的數目的。
【多態】
老習慣:
在面向對象語言中,接口的多種不同的實現方式即為多態。引用Charlie Calverts對多態的描述——多態性是允許你將父對象設置成為和一個或更多的他的子對象相等的技術,賦值之后,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作。
我們依然使用還原的辦法,多態---世間萬物的本質特性。
繼承體現了不同封裝模型(類)之間的一種關系,它源自層次分類,那么不同層次之間顯然是有聯系的,多態就是其中一個聯系,這個聯系就是本質特性。
我的描述:使用上某層來執行下某層的具體操作。
依然舉例子:所有的動物都有吃這個動作,小貓屬於動物(層次分類--繼承關系),那么小貓必然具有吃這個動作,小狗亦然。
現在喊小貓吃,貓就吃魚了,喊小狗吃,小狗就吃骨頭了。
同是吃這個操作,卻體現了不同的行為。這就是多態。
我們使用了上層的操作來執行了下層的具體操作。
上圖:
我們用繼承的結論來推導:
上層能識別下層對象,下層包含上層的操作,而下層可能有多個封裝模型(類),怎么實現用上層來描述下層的操作呢?
1.首先是繼承關系
2.用上某層指向下某層(喊小貓,喊小狗)---基類指針或引用指向派生類對象
3.使用上某層來准確執行下某層不同封裝模型的操作(吃這個動作)---虛函數
C++使用了很多語言機制來支持虛函數---每個類維護一個虛函數表、類型兼容規則等等,因為虛函數代表了運行時多態,對這個軟件的設計有着至關重要的作用。
最后我們來總結一下:
封裝:一種 對外提供服務的模型,封裝模型是對世間萬物的個體抽象。
繼承:一種 封裝模型之間關系的抽象,是不同封裝模型的層次分類。
多態:一種 不同層次分類下的重要聯系,是一種跨層操作。