-
要談面向對象編程洒宝,首先應該拿面向過程編程來對比坷牛。
舉個生活中例子,我覺得面向過程寫出來的程序就是一份炒飯(如蛋炒飯)躏将,而面向對象寫出來的程序就是一份蓋澆飯(南方木桶飯)。蛋炒飯的優(yōu)點是雞蛋和米飯蔥花等混合入味均勻吃起來香而且吃得快考蕾。但是如果你吃了一口感覺今天不想吃雞蛋祸憋,想吃肉炒飯,那因為雞蛋和米飯已經混合均勻肖卧,只能把這一份全部倒了重新炒一個肉絲炒飯蚯窥。但是如果你點的是一份蔥花雞蛋蓋澆飯,端上來發(fā)現不想吃雞蛋而想吃蔥爆肉片喜命,那比較簡單,把米飯上面的蔥花雞蛋撥掉河劝,重新蓋上一份蔥爆肉片壁榕,米飯還可以重復利用。但是蓋澆飯的缺點就是入味不均赎瞎,吃起來也比較慢牌里。
面向過程編程(炒飯)》運行性能好(吃起來方便)》耦合度高,復用性差(材料混合务甥,不區(qū)分是飯還是菜混合起來炒)牡辽。
面向對象編程(蓋澆飯)》運行性能差(吃起來不夠方便)》耦合度低,復用性好(飯菜分離)敞临。
-
面向對象的三大特性——封裝态辛、繼承、多態(tài)
【封裝】
把那些具有相同屬性和行為的對象抽象出數據變量和函數然后包裝成一個單獨的單元也就是類挺尿。設計類的過程就是在封裝奏黑。比如前面提到的做菜,可以吧米飯當做一個類编矾,菜當做一個類熟史。
【繼承】
繼承是可以讓某個類型的對象獲得另一個類型的對象的屬性和行為。一般是在一些相似的類中再抽象出相同的屬性和行為窄俏,再封裝成一個父類蹂匹,這樣可以減少代碼冗余代碼,提高復用性凹蜈。比如前面提到的做菜限寞,菜可以是更抽象的基類忍啸,蔬菜和肉菜可以是更具體一點的子類,蔬菜和肉菜都是菜昆烁,is-a關系吊骤。
注意:private修飾的類數據成員和成員方法都不能被子類繼承。
[訪問權限]:
private——只能被類中的成員函數以及友元函數訪問静尼。不能被子類繼承以及類的實例對象訪問白粉。所以數據成員一般設為private私有的。若class類成員省略了權限修飾符鼠渺,那么默認是private蛇更,如果是struct類成員則默認是public。
protected——能被類中的成員函數膛虫、友元函數舍哄、子類繼承后子類的函數訪問。但還是不能被類的實例對象訪問普舆。
public——都能訪問恬口,開放的。所以類的成員函數一般設為public沼侣,以便讓實例對象調用祖能。特別是構造函數。
friend修飾的友元函數有3種——1蛾洛、普通函數即不是某個類的成員函數养铸。2、某個類的某些成員函數轧膘。3钞螟、在類中聲明友元類friend class A; 則A的所有成員函數都成為該類的友元函數,還需注意相同class的不同對象自動互為友元谎碍。注意:友元關系不能被繼承也不能被傳遞鳞滨。友元的優(yōu)點是高效缺點是破壞了封裝。
【多態(tài)】
一個類的相同方法在不同的情形下表現出多種不同的狀態(tài)蟆淀。一般是使用父類的指針去指向子類的實例對象太援,用該父類指針去調用父類和子類的相同方法時,每次會根據子類對方法的重寫而表現出不同的狀態(tài)扳碍。比如前面提到的做菜提岔,我只說讓老板給我做一個菜,但是沒說具體什么菜笋敞,調用cooking()這個方法碱蒙,如果老板用蔬菜做了,那我就吃到了蔬菜,用葷菜做的我就吃了葷菜赛惩。
-
面向對象的幾個基本原則
【單一職責原則】
對于一個類來說哀墓,應該只有一個引起它變化的原因。如果一個類承擔的職責過多喷兼,就等于把這些職責耦合起來了篮绰,一個職責的變化可能會影響這個類完成其他的職責。比如前面提到的蛋炒飯季惯,把它當做一個類既完成做飯又完成做菜的職責吠各,兩個職責高度耦合,當想換個菜時勉抓,飯也用不了了贾漏。單一職責避免高度耦合。
【開放-封閉原則】
對擴展開放藕筋,對修改封閉纵散。是說對于一個軟件的模塊類函數等,不能修改已有的隐圾,但是可以對于新的需求擴展新的伍掀。就像香港回歸的“一國兩制”,大陸的社會主義制度不能修改暇藏,香港又暫時沒法改變原有的資本主義制度蜜笤,為了回歸的大局,那么增加一種新的制度也是可以有的叨咖。比如前面提到的做菜瘩例,對于一條草魚啊胶,剛開始只有水煮魚這一種菜甸各,現在廚師想加酸菜做成酸菜魚,與其修改水煮魚的做法焰坪,還不如擴展一個子類酸菜魚趣倾,增加一個加酸菜的方法。這里體現了可復用某饰、可擴展儒恋、靈活性好的優(yōu)點。
【依賴倒轉原則】
高層模塊不應該依賴低層模塊黔漂。而是應該將各層模塊的依賴終止于抽象類或者接口诫尽,也就是誰也不要依賴誰,除了約定的接口炬守。比如電腦的主板可以認為是高層模塊牧嫉,內存條可以認為是低層模塊,他們之間都不應該相互依賴,而是在主板上安排了內存條的接口可以輕松插拔更換內存條酣藻。比如觀察者模式中曹洽,被觀察的主題(通知者)應該依賴抽象的觀察者類,而不是具體的觀察者辽剧,同樣的具體的觀察者也應該依賴抽象的主題而不是具體的某個主題送淆。
【替換原則】在軟件里面,把父類都替換成子類怕轿,程序的行為沒有變化偷崩。
【接口分離原則】模塊間要通過抽象接口隔離開,而不是通過具體的類強耦合起來撤卢。
【高內聚环凿、低耦合】從電腦的cpu、內存條放吩、硬盤智听、主板等這些東西的關系就很好的體現了這句話。
-
類與類的幾種關系
【繼承inheritance】
類A繼承類B渡紫,UML類圖是實線加三角形由A指向B到推。A is-a B.
public繼承:父類中的成員訪問權限不變。
pretected繼承:父類的訪問權限中public權限降為protected惕澎,其他不變莉测。
private繼承:父類中public和protected權限的成員全變?yōu)閜rivate,權限降級唧喉,即子類只有類成員變量和友元函數能夠訪問這些捣卤。
【依賴dependency】
類A依賴類B,UML類圖是虛線加箭頭由A指向B八孝。A use-a B.
一般來說董朝,依賴是指A的某些方法功能要用到B,常表現為B作為A的成員方法的形參或局部變量或返回值干跛,即只和類成員方法有關子姜。
【關聯association】
類A和類B雙向關聯,UML類圖是A——B一根實線連接楼入。A與B互相作為類的數據成員變量哥捕。
【組合composition】
類A組合了類B,UML類圖是A實心菱形再實線和箭頭指向B嘉熊。類A中定義了類B作為數據成員遥赚,B在A中定義構造。A和B的對象生命周期一樣阐肤。A擁有完整的B凫佛,強擁有關系。
【聚合aggregation】
類A聚合了類B,UML類圖是A空心菱形再實線和箭頭指向B御蒲。類A中定義了類B的指針作為數據成員衣赶,類B的實例化在其他地方。A和B的對象生命周期不一樣厚满。A擁有不完整的B府瞄,弱擁有〉夤浚可以認為是 composition by reference == aggregation 或者也可以叫做委托delegation遵馆。
-
面向對象的幾個設計模式
【模板方法模式】
技巧總結:抽象父類+抽象方法+繼承+多態(tài)
大致描述:抽象父類中的抽象方法(純虛函數)可以被認為是一種模板,子類必須去實現這些抽象方法丰榴。然后利用多態(tài)性質货邓,使用父類的指針引用可以調用子類實現的不同的具體方法。 很常見的設計模式四濒,大家肯定都用過换况,只是不知道這也是一種設計模式。
【單例模式】
技巧總結:聚合了一個自身的靜態(tài)對象指針+私有構造+公有靜態(tài)getInstance()方法
大致描述:保證一個類只有一個實例盗蟆,將構造方法設為私有這樣外部不能輕易的定義構造新的對象戈二,而是提供一個共有的靜態(tài)的getInstance()方法通過類名調用來返回對象。而且可以在getInstance()方法中使用雙重鎖定來支持多線程的單例模式喳资。更簡單的是可以在該函數中定義一個局部靜態(tài)對象(依賴關系觉吭?),在局部靜態(tài)對象的創(chuàng)建過程中C++11會默認進行多線程互斥仆邓,因此不需要顯示的加鎖鲜滩。
private:
Singleton(){}; //私有構造方法
static Singleton * instance; //私有靜態(tài)對象指針,聚合自身
public:
static Singleton * getInstance()
{
if( NULL == instance)
{
mutex.lock();
if( NULL == instance)
{
instance = new Singleton(); //唯一的一次實例化對象
}
mutex.unlock();
}
return instance;
//最簡單的線程安全做法节值,上面的全不要徙硅,甚至私有的靜態(tài)對象指針都可以不要
static Singleton instance;
return &instance;
}
懶漢模式的單例模式:是指new一個實例對象發(fā)生在第一次調用getInstance()函數后,用時間換取空間察署。
餓漢模式是在該類靜態(tài)初始化的時候闷游,就在類外new了一個全局對象給類中的靜態(tài)對象指針峻汉,屬于用空間換取時間贴汪。
http://www.reibang.com/p/e47a8a778c58 單例模式的各種實現分析
【簡單工廠模式】
技巧總結:繼承+依賴+多態(tài)+前后端分離
大致描述:比如想做一個計算器。如果在一個類里面把加減乘除操作都做完休吠,那么就違背了【單一職責原則】扳埂,當發(fā)現除法功能設計的還有問題想去修改時,必須去修改整個類瘤礁,這樣也違背了【開放—封閉原則】阳懂。如果把這些做成4個子類,都繼承一個抽象的父類Operation,只有一個抽象方法getResult()岩调。這樣就算解耦了很多巷燥,此時要修改除法類也不會影響其他類。若要增加一個求次方的功能号枕,也可以增加一個次方類繼承Operation缰揪。使用的時候,在前端代碼中葱淳,可以直接創(chuàng)建各個方法的實例去實現對應的計算功能钝腺。但是,這樣做的話前端工程師也必須知道后臺算法怎么用赞厕,并沒有做好前后端開發(fā)分離艳狐。
一種好的解決前后端開發(fā)分離的方式就是,給Opration類建一個工廠類OperationFactory皿桑,工廠類依賴運算類毫目,也就是在一個create生產方法中用多態(tài)和根據參數用switch選擇判斷來new一個子類,生產方法返回父類Operation的指針诲侮。前端那邊只要知道使用這個工廠類和給定選擇條件蒜茴,就可以生產出需要的運算實例對象了,而子類運算對象創(chuàng)建的過程被封裝起來了浆西。如果要增加新的次方功能粉私,也就只需增加一個次方類和修改工廠類中的判斷加一條分支,客戶端不需要改動近零。
【工廠方法模式】
技巧總結:繼承+依賴+多態(tài)+簡單工廠升級(增加具體的工廠子類)
大致描述:在簡單工廠中诺核,我們將工廠類設計成選擇判斷動態(tài)實例化運算子類,使得這里耦合嚴重久信,每次要增加新的運算功能窖杀,都需要在工廠類的生產方法中增加新的判斷分支,這里違反了【開放—封閉原則】裙士。工廠方法模式主要就是解耦合這個工廠類入客,將每個switch分支生產一個運算子類實例的功能分離出一個工廠子類,工廠類升級成抽象父類腿椎。不過這樣做又把選擇使用哪種算法的步驟丟給了前端桌硫,也還是不夠好。
【策略模式】
技巧總結:繼承+依賴+多態(tài)+聚合(簡單工廠多一個聚合和多一個算法調用函數)
大致描述:策略模式主要是將一些具有相似功能的算法分別封裝成子類然后繼承一個抽象父類啃炸,客戶端使用這些算法時通過一個中間接口Context铆隘,完全不用知道后臺有這些算法的存在。在簡單工廠模式中南用,工廠類是只有一個create生產方法來依賴父類創(chuàng)建運算子類實例膀钠√屯澹客戶端使用的時候至少還要知道有抽象父類Operation在。而策略模式是在中間類Context中聚合了一個父類Operation對象的引用肿嘲,然后create方法中switch分支判斷生產出來一個運算子類實例對象后賦給該父類引用融击,然后再在一個getResult()成員函數中用該父類引用去調用子類的算法函數得到運算結果。如此一來雳窟,客戶端只需要知道有這么個中間類Context在就可以了砚嘴,完全不用知道后臺還有多少算法,前后端充分解耦涩拙。
【觀察者模式】
技巧總結:繼承+聚合+依賴+多態(tài)
大致描述:觀察者模式和策略模式很像际长,將多種算法換成多個觀察者,Context類換成Subject然后聚合一個list的觀察者指針兴泥,然后有attach()方法往list中增加觀察者還有detach()方法刪除觀察者工育,Subject狀態(tài)發(fā)生改變時可以調用通知方法去遍歷list調用每一個觀察者的update方法。
還有一種升級版觀察者模式是直接將每個觀察者的update()方法的函數指針組成一個list指令集搓彻,在Subject里注冊一個EventHandle去挨個執(zhí)行這些指令集如绸。這樣做的好處是將主題和觀察者解耦,主題不用再知道有哪些觀察者旭贬。