在上一篇文章中,我簡單地介紹了面向對象后面的哲學依據,在此基礎上再去理解面向對象的編程思想就會簡單得多。現在我們知道,這個世界,有概念和實體之分,概念是透過我們感官感知到的事物而進入我們的意識形成的,概念也可以認為是一種共相,代表某類事物擁有共同的屬性和特征。我們通過分類的方法將這些概念進行組織,如此,我們人類便能夠系統地區別不同的事物,并認知這個世界。
面向對象編程的核心思想
編程就是對現實世界的抽象,借助于上述哲學思想,面向對象編程定義了兩個最核心的名詞——類(class)和對象(object)。類實際上就是我們用來定義某個概念的,例如我們定義“Car”這么一個類,那么就在我們的程序世界里建立起了“Car”的概念。對象就是實體,例如我們用"Car"定義“myCar”這么一個對象,那么這個“myCar”就不再是一個概念了,而是一個獨一無二的、具體的實體,“myCar”就是指我的那輛車,不再是別的車了。這個過程和柏拉圖的理型世界有些相似,先有概念后有實體。
程序的組織方式就是分類,例如“汽車”可以分為“自動檔汽車”和“手動檔汽車”,那么我們繼承"Car",定義 “AutoCar” 和 “ManuCar” 兩個類。這里提到了繼承這個概念,待會還要詳說。
那么,通過什么來描述一個概念(類)呢?換而言之,如何定義一個類呢?
首先,任何事物都會有它的屬性。例如一個人,他(她)有區別與動物的社會屬性、勞動屬性等等,另外還會有身高、體重、年齡、性別、戶籍、民族等等屬性。屬性描述了事物的靜態特性,除此之外還需要描述事物的行為特性,這就是函數。函數(function)有功能的意思,它意味著類能完成一些功能。例如人能走路、說話、跳舞,這些就是人的行為特性,我們用函數來實現。從外界來看,人能完成走路、說話、跳舞等功能,假設這個人是機器人,那么外界就可以請求該機器人完成這些功能。簡而言之,我們通過屬性(C++中叫變量)和函數定義類。
回到之前的汽車問題:假設有一輛汽車,簡化成一個質點,從A點直線運動到B點,AB的距離為500m,汽車要經歷啟動、加速、勻速、剎車等過程,汽車有最大速度,當加速到最大速度后勻速運動,剎車距離是20m,請用面向對象的方法來描述這個問題。
這個問題中僅涉及到一個概念,即“汽車”,所以定義一個"Car"的類,描述“Car“的屬性主要有m_currVilocity(當前速度),m_maxVilocity(最大速度),m_currDistance(當前行駛距離);描述“Car“的功能行為主要有start(),accelerate(),uniformMotion(),stop()等。根據這些,我們定義"Car"這個類:
class Car{
private:
float m_currVilocity, m_maxVilocity, m_currDistance;
public:
void start();
void accelerate();
void uniformMotion();
void stop();
float getCurrVilocity();
float getMaxVilocity();
float getCurrDistance();
}
再通過"Car"這個類定義一個對象"myCar"(實體),就可以對這個對象進行操作了。
void main(){
Car myCar;
myCar.start();
while(myCar.getCurrVilocity()<myCar.getMaxVilocity()) {
myCar.accelerate();
}
while(500- myCar.getCurrDistance()<20){
myCar.uniformMotion();
}
myCar.stop();
}
到此,我們已經完成了簡單的面向對象的編程,這時候初學者一定會納悶,這樣搞比面向過程的方法麻煩多了,有什么好的。這個問題暫時擱置一下,先看看面向對象的三個基本特性。
面向對象的三個基本特性
面向對象的三個基本特性分別是封裝性、繼承性、多態性。
(1)封裝性。比較細心的同志會發現我在定義屬性的時候使用了"private"這個關鍵詞,這表示這些屬性不能被外界直接訪問和修改,這就是封裝性。之所以這么做,是為了隱藏復雜性,包括兩方面的原因:一是對客戶隱藏他們不需要知道的細節,讓客戶專注到如何使用接口上來,這也可以防止他們窺探類的內部設計思想;二是允許庫設計人員修改內部結構,不用擔心它會對客戶程序員造成什么影響,只要對外接口不變,類的內部變化不會對外界造成任何影響。所以封裝讓類保持了一定的獨立性,有利于設計高內聚、低耦合的程序。
(2)繼承性。上面提到了 “汽車”可以分為“自動檔汽車”和“手動檔汽車”,所以能繼承"Car",定義 “AutoCar” 和 “ManuCar” 兩個類,這就是繼承性。 這里,"Car"被稱為父類,而 “AutoCar” 和 “ManuCar” 稱為子類,子類會繼承父類的所有屬性和函數,所以 “AutoCar” 和 “ManuCar” 就復用了"Car"的所有屬性和功能。但繼承不止有類的繼承的這種方式,在JAVA里面還有接口的繼承,這個留給“JAVA與C++的區別”這篇文章。另外,繼承性也不僅僅只體現在繼承上,還可以通過“組合”來實現,而且有時候使用組合會帶來更好的程序結構,這個也留到以后再寫。
(3)多態性。“AutoCar” 和 “ManuCar” 兩個類純粹繼承"Car"還不夠,否則就和"Car"一模一樣了。我們知道,自動檔汽車和手動檔汽車主要區別在于加速的方式不一樣,所以就要重寫accelerate()這個函數,通過重寫,“AutoCar”定義的對象和 “ManuCar”定義的對象再加速時就會呈現不同的結果,這就是多態性。程序的多態性還包括“函數重載”,在C++里面,允許存在多個同名函數,但是參數表不同,或者參數類型不同,或者兩者都不同,這就是“函數重載”。
了解了三個基本特性后,再回到之前那個問題,很多新學面向對象語言的人都會覺得面向對象要比面向過程麻煩多了,我當初也是這種感覺。但是當問題復雜一些時:汽車廠升級了該款汽車,加速度和最大速度都要都比改進的汽車大,廠家想通過一次比賽試驗汽車的改進效果,于是改進前的汽車和改進后的汽車從A點同時出發,看哪一輛先到達B點。
用面向對象的方法輕而易舉就完成了,我通過Car 定義兩個對象就行了,一個是改進前的car1,一個是改進后的car2,這兩個Car只是屬性有所不同而已,然后就可以對這兩個Car進行操作了。
更復雜的情況:汽車增加新功能,可以轉彎壁障,另外路上不只有這兩輛車,還有公交車、貨車、自行車,路面也不這么簡單了,分成了私家車車道、公交車車道、非機動車車道和人行道,還有紅綠燈、指示牌等交通管理設備。
公交車、貨車、自行車等都是交通工具,定義一個交通工具的類,找到他們的共有屬性和行為,在交通工具這個類中定義,然后再繼承交通工具的類,定義公交車、貨車、自行車,找到他們的不同點,添加各自獨特的屬性和函數,改寫與父類有區別的。然后再定義公路、交通管理設備等類,完成這些類的定以后,再根據實際情況定義不同的對象,對象之間進行交互,就能把這個復雜的問題給解決了,這就是封裝性、繼承性、多態性帶來的神奇力量!
本文主要介紹了面向對象編程的核心思想和三個基本特性,但要入門面向對象編程知道這些還遠遠不夠,還有許多需要學習和掌握的,且聽下回分解。