出處:希賽網(wǎng)
不同的人在談面向?qū)ο缶幊蹋∣OP)時(shí)所指的含義并不相同。有人認(rèn)為任何采用圖形界面的應(yīng)用程序都是面向?qū)ο蟮?。有人把它作為術(shù)語來描述一種特別的進(jìn)程間通信機(jī)制。還有人使用這個(gè)詞匯是另有深義的,他們其實(shí)是想說:“來啊,買我的產(chǎn)品吧!”我一般不提OOP,但只要提到,我的意思是指使用繼承和動(dòng)態(tài)綁定的編程方式。 --《C++沉思錄》
《C++沉思錄》說的是十幾年前的事了,現(xiàn)在大家對(duì)面向?qū)ο蟮幕卮鹨呀?jīng)是眾口一詞:封裝、繼承和多態(tài)。大家都知道,在面向?qū)ο笾?,一輛汽車是一個(gè)對(duì)象,汽車這個(gè)概念是一個(gè)類。汽車有漂亮的外觀,把各種內(nèi)部原理都隱藏起來了,司機(jī)不必知道它的內(nèi)部工作原理仍然能開車,即使汽車隨技術(shù)的進(jìn)步不斷升級(jí),對(duì)司機(jī)也沒有什么影響,這就是封裝的好處。
汽車是交通工具的一種,汽車是一個(gè)類,交通工具也是一個(gè)類,而交通工具類包括了汽車類,從而具有更廣泛的意義。這種從抽象到具體的關(guān)系就是繼承關(guān)系,我們可以說汽車類繼承了交通工具類,汽車類是交通工具類的子類,交通工具類是汽車類的父類。
作為交通工具,它肯定可以運(yùn)動(dòng)(move),從甲地運(yùn)動(dòng)到乙地,就起到了交通的作用。輪船是一種交通工具,所以輪船類也是交通工具類的子類。同樣是運(yùn)動(dòng),輪船的運(yùn)動(dòng)和汽車的運(yùn)動(dòng)方式肯定有所不同,這樣以不同的方式完成同樣的功能就叫多態(tài)。
關(guān)于對(duì)象:對(duì)象就是某一具體的事物,比如一個(gè)蘋果, 一臺(tái)電腦都是一個(gè)對(duì)象。每個(gè)對(duì)象都是唯一的,兩個(gè)蘋果,無論它們的外觀有多么相像,內(nèi)部成分有多么相似,兩個(gè)蘋果畢竟是兩個(gè)蘋果,它們是兩個(gè)不同的對(duì)象。對(duì)象可以是一個(gè)實(shí)物,也可能是一個(gè)概念,比如某個(gè)蘋果對(duì)象是實(shí)物,而一項(xiàng)政策可能就是一個(gè)概念性的對(duì)象了。
關(guān)于類:對(duì)象可能是一個(gè)無窮的集合,用枚舉的方式來表示對(duì)象集合不太現(xiàn)實(shí)。抽象出對(duì)象的特征和功能,按此標(biāo)準(zhǔn)將對(duì)象分類,這就引入類的概念。類就是一類事物的統(tǒng)稱,類實(shí)際上就是一個(gè)分類的標(biāo)準(zhǔn),符合這個(gè)分類標(biāo)準(zhǔn)的對(duì)象都屬于這個(gè)類。當(dāng)然,為了方便起見,通常只需要抽取那些,對(duì)當(dāng)前應(yīng)用來說是有用的特征和功能。
關(guān)于抽象類:類是對(duì)對(duì)象的抽象,比如,蘋果是對(duì)所有具體的蘋果的抽象。如果我們對(duì)蘋果這個(gè)類進(jìn)行一步抽象,可以得到一個(gè)水果類。這種對(duì)類本身進(jìn)行抽象而得到的類,就是抽象類。抽象類不像普通類,它是沒有對(duì)象與之對(duì)應(yīng)的。像蘋果類,你總是可以拿到一個(gè)叫蘋果的東西,而對(duì)于水果類,根本沒一個(gè)真正叫水果的東西。你可以說一個(gè)蘋果是一個(gè)水果,從邏輯上講沒有錯(cuò),但沒有什么意義。一般在程序中,抽象類是不能實(shí)例化的。
關(guān)于面向?qū)ο螅好嫦驅(qū)ο缶褪且詫?duì)象為中心。為什么不說是面對(duì)類,而說是面向?qū)ο竽??類是?duì)象的集合,考慮類實(shí)際上也是在考慮對(duì)象,有時(shí)甚至并不嚴(yán)格的區(qū)分它們。所以說面向?qū)ο笠辉~比面向類更確切。
既然以對(duì)象為中心,面向?qū)ο笏紤]的內(nèi)容自然是對(duì)象、對(duì)象間的協(xié)作、對(duì)象的分類、類之間的關(guān)系等等,由此引申了出幾個(gè)重要的概念。
1. 封裝
what:對(duì)象也有隱私,對(duì)象的隱私就是對(duì)象內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)。要想對(duì)象保持良好的形象就要保護(hù)好對(duì)象隱私,所謂的封裝其實(shí)就是保護(hù)對(duì)象隱私。當(dāng)然,沒有人能完全隱藏自己的隱私,比如你去轉(zhuǎn)戶口時(shí),你不得不透露自己的家庭信息和健康狀況。另外,在不同的場(chǎng)合所透露隱私的數(shù)量也不一樣,朋友和家人可能會(huì)知道你更多隱私,同事次之,其他人則知道得更少。面向?qū)ο笠部紤]了這些實(shí)際的情況,所以像C++之類的語言有public/private/protected/friend等關(guān)鍵字,以適應(yīng)于不同的情況。
why:封裝可以隔離變化。據(jù)以往的經(jīng)驗(yàn),我們知道內(nèi)部實(shí)現(xiàn)是容易變化的,比如電腦在不斷的升級(jí),機(jī)箱還是方的,但里面裝的CPU和內(nèi)存已是今非昔比了。變化是不可避免的,但變化所影響的范圍是可以控制的,不管CPU怎么變,它不應(yīng)該影響用戶使用的方式。封裝是隔離變化的好辦法,用機(jī)箱把CPU和內(nèi)存等等封裝起來,對(duì)外只提供一些標(biāo)準(zhǔn)的接口,如USB插口、網(wǎng)線插口和顯示器插口等等,只要這些接口不變,內(nèi)部怎么變,也不會(huì)影響用戶的使用方式。
封裝可以提高易用性。封裝后只暴露最少的信息給用戶,對(duì)外接口清晰,使用更方便,更具用戶友好性。試想,如果普通用戶都要知道機(jī)箱內(nèi)部各種芯片和跳線,那是多么恐怖的事情,到現(xiàn)在為止我甚至還搞不清楚硬盤的跳線設(shè)置,幸好我沒有必要知道。
how:在C語言中,可以用結(jié)構(gòu)+函數(shù)來模擬類的實(shí)現(xiàn),而用這種結(jié)構(gòu)定義的變量就是對(duì)象。封裝有兩層含義,其一是隱藏內(nèi)部行為,即隱藏內(nèi)部函數(shù),調(diào)用者只能看到對(duì)外提供的公共函數(shù)。其二是隱藏內(nèi)部信息,即隱藏內(nèi)部數(shù)據(jù)成員?,F(xiàn)在都建議不要對(duì)外公開任何數(shù)據(jù)成員,即使外部需要知道的數(shù)據(jù)成員,也只能通過函數(shù)獲取。
在C語言中要隱藏內(nèi)部函數(shù)很簡單:不要它把放在頭文件中,在C文件中定義時(shí),前面加static關(guān)鍵字,每個(gè)類放在獨(dú)立的文件中。這樣可以把函數(shù)的作用范圍限于當(dāng)前文件內(nèi),當(dāng)前文件只有類本身的實(shí)現(xiàn),即只有當(dāng)前的類自己才能看到這些函數(shù),這就達(dá)到了隱藏的目的。
在C語言中要隱藏?cái)?shù)據(jù)成員較為麻煩,它沒有提供像C++中所擁有的public/protected/friend/private類似的關(guān)鍵字。只能通過一些特殊方法模擬部分效果,我常用的方法有兩種。
其一是利用C的特殊語法,在頭文件中提前聲明結(jié)構(gòu),在C文件才真正定義它。這樣可以把結(jié)構(gòu)的全部數(shù)據(jù)信息都隱藏起來。因?yàn)橥獠坎恢缹?duì)象所占內(nèi)存的大小,所以不能靜態(tài)的創(chuàng)建該類的對(duì)象,只能調(diào)用類提供的創(chuàng)建函數(shù)才能創(chuàng)建。這種方法的缺陷是不支持繼承,因?yàn)樽宇愔械貌坏饺魏侮P(guān)于父類的信息。
其二是把私有數(shù)據(jù)信息放在一個(gè)不透明的priv變量中。只有類的實(shí)現(xiàn)代碼才知道priv的真正定義。
2. 繼承
what: 繼承描述的是一種抽象到具體的關(guān)系。具體的東西繼承了抽象的東西的特性,比如說,水果這個(gè)概念比蘋果這個(gè)概念更抽象,其意義更具有一般性,而蘋果這個(gè)概念則更具體,其意義更狹窄一些,在面向?qū)ο罄?,我們可以說蘋果類繼承了水果類。繼承是指繼承了父類的特性,繼承本質(zhì)是源于分類學(xué),細(xì)的分類繼承大分類的特性。
why: 繼承描述了抽象到具體的關(guān)系,所以能夠有效利用抽象這件武器來戰(zhàn)勝軟件的復(fù)雜性。抽象在實(shí)現(xiàn)中無處不在,類就是對(duì)事物的抽象,提到蘋果你就想到蘋果這一類事物,無需要關(guān)心其大小、顏色和成分,蘋果這兩個(gè)字就足夠了。名不正則言不順,言不順則事不成,看來老夫子已經(jīng)領(lǐng)悟到了抽象的威力。
繼承不但利用了抽象的力量來降低系統(tǒng)的復(fù)雜性,它還提供了一種重用的方式。假設(shè)我們承認(rèn)下列面這個(gè)繼承關(guān)系,蘋果繼承了水果,水果繼承了食物,如果我們已經(jīng)知道什么是食物,什么是水果,在描述蘋果時(shí),沒有必要去重復(fù)講解食物和水果的概念了,這就是重用,重用了對(duì)水果和食物兩個(gè)概念的理解。
how: 在C語言中實(shí)現(xiàn)繼承很簡單,可以用結(jié)構(gòu)來模擬。這種實(shí)現(xiàn)基于一個(gè)明顯的事實(shí),結(jié)構(gòu)在內(nèi)存中的布局與結(jié)構(gòu)的聲明具有一致的順序。我們知道在程序描述事物的特征時(shí),主要通過數(shù)據(jù)變量描述事物的屬性特征,如顏色、重量和體積等,用函數(shù)來描述事物的行為特征,和運(yùn)動(dòng)、成長和搏斗等。
繼承
繼承在現(xiàn)實(shí)世界中應(yīng)用很廣,在程序里也是一樣,甚至可以說是過度使用了。多年以前一些大師已經(jīng)提出,優(yōu)先使用組合而不是繼承。主要原因有三點(diǎn),首先是多級(jí)繼承和多重繼承太復(fù)雜了,失去了抽象帶來的簡潔性。其次是父類與子類之間共享太多信息,它們的耦合太緊密。三是父類與子類之間的關(guān)系在編譯時(shí)就靜態(tài)綁定了,很難做到在運(yùn)行時(shí)多態(tài)。
現(xiàn)在一般都提倡,只繼承接口不繼承實(shí)現(xiàn),通過組合達(dá)到代碼重用的目的。在《設(shè)計(jì)模式》中是這樣強(qiáng)調(diào)的,在MS的COM里也是這樣做的。所以我基本上只使用接口繼承,很少遇到什么麻煩,建議大家也遵循這一準(zhǔn)則。
3. 多態(tài)
what: 盡管多態(tài)這個(gè)詞本身就表明了它所代表的意義,但還是讓初學(xué)者感到多少有些神秘。多態(tài)就是完成相同功能的多種方式,比如拿動(dòng)物的運(yùn)動(dòng)來說吧,鳥的運(yùn)動(dòng)通常是飛,魚的運(yùn)動(dòng)通常是游,陸上動(dòng)物的運(yùn)動(dòng)通常是跑,同是運(yùn)動(dòng),但方式不一樣,這就是多態(tài)。不少人對(duì)多態(tài)的回答是,允許同名函數(shù)存在。這種回答顯然沒有抓住多態(tài)的本質(zhì)。
why: 關(guān)于動(dòng)物運(yùn)動(dòng)這個(gè)例子,可能無法展示多態(tài)的好處。我們來考慮另外一個(gè)多態(tài)的例子,U盤。U盤的技術(shù)含量可能不是很高,有很多廠家都在設(shè)計(jì)和生產(chǎn),就是說U盤有多種不同的實(shí)現(xiàn),這就是U盤的多態(tài)。U盤的多態(tài)性對(duì)消費(fèi)者來說是有好處的,選擇多了,你可以在價(jià)格、質(zhì)量和外觀等方式做出平衡,選擇你中意的U盤。多態(tài)的前提是接口的一致性,否則多態(tài)造成的麻煩遠(yuǎn)勝于它帶來的好處。不管U盤的體積、顏色和質(zhì)量如何,它都必需遵循相應(yīng)的USB標(biāo)準(zhǔn),這些U盤在任何帶USB接口的電腦上都可以使用。
how: 多態(tài)在C語言中通常用函數(shù)指針來實(shí)現(xiàn),函數(shù)指針定義了函數(shù)的原型,即它的參數(shù)和返回值的描述,以及函數(shù)的意義,不同的函數(shù)可以有相同的函數(shù)原型,比如排序函數(shù),無論是快速排序還是歸并排序,它們的實(shí)現(xiàn)不一樣,但函數(shù)原型可以一樣。在不同的情況下,讓函數(shù)指針到不同的函數(shù)實(shí)現(xiàn)上,這就實(shí)現(xiàn)了多態(tài)。
類的三個(gè)層次:
類這個(gè)概念比較微妙,即使在軟件開發(fā)領(lǐng)域,不同的人提到這個(gè)概念所指的內(nèi)容也不一樣。一些大師早就注意到了這一點(diǎn),為了讓這個(gè)概念在不同情況下,具有較準(zhǔn)確的意義,他們建議從三個(gè)層次看待類這個(gè)概念:
1. 概念層(Conceptual)
這是一個(gè)較高的層次,通常在進(jìn)行領(lǐng)域分析時(shí),為了建立概念模型時(shí)使用。這時(shí)使用的術(shù)語是現(xiàn)實(shí)世界中的術(shù)語,而不是軟件開發(fā)中的術(shù)語。在這個(gè)層次,類只是一個(gè)概念,加上一些不太嚴(yán)謹(jǐn)?shù)奶卣髡f明,甚至只有一個(gè)名稱。盡管它往往與軟件開發(fā)中的類一一對(duì)應(yīng),便這種映射并不一定是直接的。
2. 規(guī)格層(Specification)
在這個(gè)層次,類已經(jīng)是屬于軟件開發(fā)范疇了,但主要關(guān)注的是類的接口,而不是類的實(shí)現(xiàn)。此時(shí)你可能想到它的一組接口函數(shù),而不關(guān)心這些函數(shù)是如何實(shí)現(xiàn)的。
3. 實(shí)現(xiàn)層(Implementation)
在這個(gè)層次,我們才真正關(guān)注類的實(shí)現(xiàn),此時(shí)你可能會(huì)想到一些用某種語言寫成的函數(shù)體,定義的成員變量等等。
面向?qū)ο蟮暮锰帲?/p>
面向?qū)ο笠呀?jīng)征服了軟件開發(fā)的絕大部分領(lǐng)域,近幾年來出現(xiàn)的面向方面的編程(AOP)、產(chǎn)生式編程(GP)和面向組件的開發(fā)等等,都提出了一些新的思維,在某些方面大提高了開發(fā)效率,但它們并非是取代了面向?qū)ο?,相反是?duì)面向?qū)ο蟮难a(bǔ)充和完善,面向?qū)ο笫冀K穩(wěn)坐第一把交椅。
面向?qū)ο蟮降子泻蔚潞文?,它憑借什么取代面向?qū)Τ棠兀糠庋b、繼承和多態(tài)到底有何種魔力,吸引眾多的高手去完善它,讓布道者們不厭其煩的頌揚(yáng)它呢?歸根結(jié)底,面向?qū)ο髸?huì)帶來兩個(gè)好處,這兩個(gè)好處正是設(shè)計(jì)者們一直在追求的:
1. 降低系統(tǒng)的復(fù)雜度。
眾所周知,隨著硬件的飛速發(fā)展,計(jì)算機(jī)的計(jì)算能力越來越強(qiáng)大,人們對(duì)軟件期望也越來越高,而軟件復(fù)雜度又與它的規(guī)模成指數(shù)倍數(shù)增長。軟件復(fù)雜度可以說是軟件開發(fā)的第一大難題,我們可以輕而易舉的寫出5000行代碼,而面對(duì)100萬行代碼規(guī)模的軟件,會(huì)有點(diǎn)讓人覺得人的智力是多么有限。
而面向?qū)ο笳墙档拖到y(tǒng)復(fù)雜度的好方法。首先它按類來組織系統(tǒng),把系統(tǒng)分成幾個(gè)大的部分,每個(gè)部分又由更小的子類組成,如此細(xì)分下去直到我們能輕易實(shí)現(xiàn)它為此,這種分而治之的方法符合人類解決復(fù)雜問題的習(xí)慣。
其次是它采用從抽象到具體的順序來把握事物,抽象讓我們用少量精力先掌握事物的共性,然后再去研究事物更具體的特性,這種逐漸細(xì)化的方法也是符合人類解決復(fù)雜問題的習(xí)慣的。
2. 隔離變化。
需求變化和技術(shù)變化也是軟件開發(fā)所面臨的兩大難題。用戶似乎從來不知道什么是他們真正的需求,或許他們真正的需求也是在變化的。技術(shù)可謂日新月異,我們不斷的發(fā)明新技術(shù),這些技術(shù)幫我們提高了生產(chǎn)力。但有時(shí)新技術(shù)也會(huì)砸到自己的腳,為了運(yùn)用這些新技術(shù),我們要花更多時(shí)間學(xué)習(xí)和工作。或者說創(chuàng)新只是滿足了少數(shù)人的樂趣,讓多數(shù)人吃了苦頭。
只有變化才是永恒的,詛咒變化無濟(jì)于事,面對(duì)它并搞掂它才是正途。大師也建議我們?nèi)肀ё兓?,而不是逃避變化。面向?qū)ο笳菗肀ё兓你y彈之一,它不是嘗試阻止變化的發(fā)生(對(duì)此誰也無能為力),而是努力去隔離變化。與整體比較,變化的部分畢竟只占很小的分量,如果這些變化被隔離開了,它不影響不變的部分的,變的部分獨(dú)立變化,不會(huì)牽一發(fā)而動(dòng)全身,這可以大大減少變化引起的麻煩。針對(duì)接口編程而不是針對(duì)實(shí)現(xiàn)編程,這是面對(duì)象的精髓,也是擁抱變化的良方。