我問(wèn)占小狼到底什么是面向?qū)ο缶幊??他轉(zhuǎn)頭就走。
你好,我是 yes。
面向?qū)ο缶幊滔氡卮蠹叶级炷茉敚菍?xiě)了這么多代碼你對(duì)面向?qū)ο笥星逦恼J(rèn)識(shí)嗎?
來(lái)看看這幾個(gè)問(wèn)題:
-
到底什么是面向?qū)ο缶幊蹋?/p>
-
和面向過(guò)程編程有什么區(qū)別?
-
什么又稱(chēng)為面向?qū)ο笳Z(yǔ)言、面向過(guò)程語(yǔ)言?
-
用面向?qū)ο笳Z(yǔ)言寫(xiě)的代碼就面向?qū)ο罅耍?/p>
-
面向?qū)ο缶幊陶娴木瓦@么好嗎?
-
復(fù)雜的業(yè)務(wù)用面向?qū)ο缶幊叹秃线m了嗎?
我還真沒(méi)具體地定義過(guò)到底什么是面向?qū)ο缶幊獭?/p>
所以假設(shè)有人問(wèn)到底什么是面向?qū)ο缶幊??有什么好處?/p>
一時(shí)還真不知道怎么說(shuō),或者說(shuō)成體系的解釋。
這篇文章我就談?wù)勎业睦斫?,也試著看能不能說(shuō)清啥叫面向?qū)ο缶幊獭?/p>
正文
從二進(jìn)制命令到匯編語(yǔ)言。
從匯編語(yǔ)言到面向過(guò)程語(yǔ)言再到面向?qū)ο笳Z(yǔ)言。
計(jì)算機(jī)語(yǔ)言的發(fā)展是為了便于人類(lèi)的使用,使其更符合人類(lèi)的思考方式。
計(jì)算機(jī)的思路就是取指執(zhí)行,一條直道走到底,它可不會(huì)管你什么抽象,不管什么業(yè)務(wù)建模,通通得給它變成一條條指令,排好順序讓它執(zhí)行。
而我們?nèi)祟?lèi)不一樣,我們的思維在簡(jiǎn)單場(chǎng)景來(lái)看是一條道,但在復(fù)雜場(chǎng)景就需要做各種分類(lèi),才能理清楚關(guān)系,處理好事務(wù)。
就像法庭,分為法官、書(shū)記員、法警、原告、被告、證人等角色。
這么多人分好類(lèi),按照法庭審理各司其職,一個(gè)案子才能高效、順利得審判。
再回到計(jì)算機(jī)語(yǔ)言來(lái),匯編我就不說(shuō)了,面向過(guò)程其實(shí)就是一條道的思路,因?yàn)槠鸪蹙褪前从?jì)算機(jī)的思路來(lái)編寫(xiě)程序。
我就拿用咖啡機(jī)煮咖啡為例,按照面向過(guò)程的流程是:
-
執(zhí)行加咖啡豆方法 -
執(zhí)行加水方法 -
執(zhí)行煮咖啡方法 -
執(zhí)行喝咖啡方法
很簡(jiǎn)單直觀的操作,你可能沒(méi)什么感覺(jué),我再按面向?qū)ο笏枷雭?lái)分析下這個(gè)流程。
在執(zhí)行煮咖啡操作前要抽象出:人和咖啡機(jī)(分類(lèi)),然后開(kāi)始執(zhí)行:
-
人.加咖啡豆 -
人.加水 -
咖啡機(jī).煮 -
人.喝咖啡
是不是有點(diǎn)感覺(jué)了?
面向過(guò)程,從名字可以得知重點(diǎn)是過(guò)程,而面向?qū)ο蟮闹攸c(diǎn)是對(duì)象。
從這個(gè)例子可以看出兩者的不同:面向過(guò)程是很直接的思維,一步步的執(zhí)行,一條道走到底。
而面向?qū)ο笫窍瘸橄螅咽挛锓诸?lèi)得到不同的類(lèi),劃分每個(gè)類(lèi)的職責(zé),暴露出每個(gè)類(lèi)所能執(zhí)行的動(dòng)作,然后按邏輯執(zhí)行時(shí)調(diào)用每個(gè)類(lèi)的方法即可,不關(guān)心內(nèi)部的邏輯。
從例子可以看出面向?qū)ο缶幊虉?zhí)行的步驟沒(méi)有變少,整體執(zhí)行流程還是一樣的,都是先加咖啡豆、加水、煮咖啡、喝,這個(gè)邏輯沒(méi)有變。
無(wú)非就是劃分了類(lèi),把每一步驟具體的實(shí)現(xiàn)封裝了起來(lái),散布在不同的類(lèi)中。
對(duì)我們程序員來(lái)說(shuō)是最最直接的感受:變的其實(shí)就是代碼的分布,煮咖啡的代碼實(shí)現(xiàn)被封裝在咖啡機(jī)內(nèi)部,喝咖啡的代碼實(shí)現(xiàn)被封裝在人內(nèi)部,而不是在一個(gè)方法中寫(xiě)出來(lái)。
代碼的分布確實(shí)是最直觀的,但是變得不僅只是分布,而是思想上的變化。
就是上面提到的計(jì)算機(jī)思維到人類(lèi)思維的變化。
我認(rèn)為這個(gè)變化是因?yàn)檐浖陌l(fā)展,業(yè)務(wù)越來(lái)越復(fù)雜。
人們用面向過(guò)程語(yǔ)言編寫(xiě)復(fù)雜的軟件時(shí),需要按照不同的功能把一些數(shù)據(jù)和函數(shù)放到不同的文件中,漸漸地人們就發(fā)現(xiàn)這不就是先分類(lèi)嗎?
并且好像業(yè)務(wù)分析下來(lái)都能和現(xiàn)實(shí)世界的東西對(duì)應(yīng)上?
于是人們慢慢地總結(jié)、提煉就演變成了面向?qū)ο?,再根?jù)面向?qū)ο蟮奶匦蕴釤挸鲫P(guān)鍵點(diǎn):封裝、繼承和多態(tài)。
而這個(gè)面向?qū)ο笏枷刖皖?lèi)似我們?nèi)祟?lèi)面對(duì)復(fù)雜場(chǎng)景時(shí)候的分析思維:歸類(lèi)、匯總。
所以面向?qū)ο缶幊叹统蔀榱爽F(xiàn)在主流的編程風(fēng)格,因?yàn)榉先祟?lèi)的思考方式。
面向過(guò)程編程和面向?qū)ο缶幊虖乃枷肷系淖兓牵簭挠?jì)算機(jī)思維轉(zhuǎn)變成了人類(lèi)的思維來(lái)編寫(xiě)編碼。
所以我們知道面向?qū)ο缶幊唐鋵?shí)是一種進(jìn)步,一種更貼近人類(lèi)思考方式的編碼風(fēng)格,是源于人們用面向過(guò)程編程時(shí)的經(jīng)驗(yàn)總結(jié)。
至此我們知道了面向?qū)ο缶幊痰膩?lái)源,相信知曉了來(lái)源能更好的理解面向?qū)ο蟆?/p>
那到底什么是面向?qū)ο缶幊蹋?/span>
面向?qū)ο缶幊?Object Oriented Programming,OOP)是一種編程范式或者說(shuō)編程風(fēng)格。
學(xué)術(shù)一點(diǎn)講就是把類(lèi)或?qū)ο笞鳛榛締卧獊?lái)組織代碼,并且運(yùn)用提煉出的:封裝、繼承和多態(tài)來(lái)作為代碼設(shè)計(jì)指導(dǎo)。
這其實(shí)就是面向?qū)ο缶幊獭?/p>
其實(shí)從上面煮咖啡的流程應(yīng)該能 get 到這個(gè)含義了。
OOP 說(shuō)白了就是拿到需求開(kāi)始分析,進(jìn)行抽象建立業(yè)務(wù)模型,每個(gè)模型建立對(duì)應(yīng)的類(lèi)。
思考業(yè)務(wù)的交互,根據(jù)交互定義好接口并做好接口的控制訪問(wèn),將于此類(lèi)相關(guān)的數(shù)據(jù)和動(dòng)作都封裝起來(lái)。
抽象出父類(lèi),子類(lèi)繼承父類(lèi)來(lái)進(jìn)行代碼的復(fù)用和擴(kuò)展。
執(zhí)行功能時(shí)用父類(lèi)來(lái)調(diào)用,在實(shí)際代碼運(yùn)行過(guò)程會(huì)進(jìn)行動(dòng)態(tài)綁定,調(diào)用子類(lèi)的實(shí)現(xiàn)達(dá)到多態(tài)的特性。
多態(tài),學(xué)術(shù)點(diǎn)講就是:運(yùn)行時(shí)用相同的代碼根據(jù)不同類(lèi)型的實(shí)例呈現(xiàn)出不同行為的現(xiàn)象。
如果有新功能要實(shí)現(xiàn),只需要?jiǎng)?chuàng)建一個(gè)新子類(lèi),以前的執(zhí)行邏輯不需要發(fā)生變化,這就是「開(kāi)閉原則」,對(duì)修改關(guān)閉,對(duì)擴(kuò)展開(kāi)放”。
來(lái)簡(jiǎn)單的看個(gè)代碼可能會(huì)有更直觀的感受,沒(méi)記錯(cuò)的話大學(xué)時(shí)也是拿動(dòng)物舉例。
狗是動(dòng)物、鴨子是動(dòng)物,所以有個(gè) Animal 類(lèi)。
然后能發(fā)聲,所以有 voice 方法。
????public?class?Animal?{
??????public?void?voice(){
??????????System.out.println("動(dòng)物的叫聲");
??????}
????}
然后搞個(gè) Dog、Duck 繼承 Animal 實(shí)現(xiàn)各自的 voice。
????public?class?Dog?extends?Animal?{
??????public?void?voice(){
?????????System.out.println("汪汪汪~(yú)");
??????}
????}
???public??class?Duck?extends?Animal?{
??????public?void?voice(){
?????????System.out.println("gagaga~");
??????}
????}
然后到時(shí)候就可以實(shí)例化不同的對(duì)象來(lái)達(dá)到多態(tài)的效果。
????public?class?Test{
??????private?Animal?animal;
??????public?void?setAnimal(Animal?animal)?{
????????this.animal?=?animal;
??????}
??????public?void?voice(){
??????????animal.voice();
??????}
????}
多態(tài)帶來(lái)的好處,無(wú)非就是 Test 里面代碼不用動(dòng),你想要狗叫你就 new Dog 然后 set 進(jìn)去,如果要鴨子就 ?new Duck 然后 set 進(jìn)去。
如果加入了新動(dòng)物那就建一個(gè)新動(dòng)物類(lèi) set 進(jìn)去就行,符合開(kāi)閉原則。
和面向過(guò)程編程有什么區(qū)別?
其實(shí)從上面煮咖啡和動(dòng)物的這兩個(gè)例子應(yīng)該能感受出來(lái)區(qū)別。
最重要的是思想上的區(qū)別,上面也已經(jīng)提到了。
還有一點(diǎn)就是數(shù)據(jù)和動(dòng)作。
面向過(guò)程編程這種編程風(fēng)格是以過(guò)程作為基本單元來(lái)組織代碼的,過(guò)程其實(shí)就是動(dòng)作,對(duì)應(yīng)到代碼中來(lái)就是函數(shù),面向過(guò)程中函數(shù)和數(shù)據(jù)是分離的,數(shù)據(jù)其實(shí)就是成員變量。
而面向?qū)ο缶幊痰念?lèi)中數(shù)據(jù)和動(dòng)作是在一起的,這也是兩者的一個(gè)顯著的區(qū)別。
什么又稱(chēng)為面向?qū)ο笳Z(yǔ)言、面向過(guò)程語(yǔ)言
面向?qū)ο笳Z(yǔ)言其實(shí)就是有現(xiàn)成的語(yǔ)法機(jī)制來(lái)支持類(lèi)、對(duì)象的語(yǔ)言,比如 Java。
當(dāng)然還要有支持繼承、多態(tài)的語(yǔ)法機(jī)制。
面向過(guò)程語(yǔ)言就反著理解,沒(méi)有現(xiàn)成的語(yǔ)法機(jī)制來(lái)支持類(lèi)、對(duì)象等基本單元來(lái)組織代碼。
當(dāng)然不是你用了面向?qū)ο笳Z(yǔ)言寫(xiě)出來(lái)的代碼就面向?qū)ο罅恕?/p>
你要通篇就一個(gè) class,一堆雜亂無(wú)章都往里面塞,不歸類(lèi)、沒(méi)有封裝的意識(shí),一條直到,這可不叫面向?qū)ο缶幊獭?/p>
當(dāng)然也不是用面向過(guò)程語(yǔ)言就寫(xiě)不出面向?qū)ο蟮拇a,只是由于語(yǔ)法層面的不支持,寫(xiě)起來(lái)沒(méi)那么方便,需要用一些手段,具體就不展開(kāi)了。
所以語(yǔ)言只是為了更好的支持編程范式,重要的還是思想上的轉(zhuǎn)變。
面向?qū)ο缶幊陶娴木瓦@么好嗎?
結(jié)論先上:軟件設(shè)計(jì)沒(méi)有銀彈,沒(méi)有最好的,只有合適的。
前面也提到了面向?qū)ο蟾先祟?lèi)的思考方式,這其實(shí)就是優(yōu)勢(shì),能 hold 住復(fù)雜的需求。
復(fù)雜的需求關(guān)系都是錯(cuò)綜復(fù)雜的,我們分類(lèi)、抽象、封裝就能得到一個(gè)個(gè)規(guī)范化的模塊(類(lèi))。
大型項(xiàng)目都需要很多人協(xié)同合作,因?yàn)閯澐值那逦?,每個(gè)人只要實(shí)現(xiàn)自己負(fù)責(zé)的模塊。
然后根據(jù)模塊之間關(guān)系再組裝起來(lái)即可。
脈絡(luò)清晰也使得我們開(kāi)發(fā)的時(shí)候思路也異常的清晰,提升開(kāi)發(fā)的效率。
并且由于封裝的特性,類(lèi)的內(nèi)部是高度內(nèi)聚的,會(huì)利用訪問(wèn)控制權(quán)限暴露出有限的訪問(wèn),這使得類(lèi)內(nèi)部的數(shù)據(jù)不會(huì)被隨意更改,提高代碼的維護(hù)性。
還有前面提到的繼承,提高代碼的復(fù)用性,由繼承實(shí)現(xiàn)的多態(tài)也符合開(kāi)閉原則。
我還看過(guò)一個(gè)很形象的解釋?zhuān)ê芫弥翱催^(guò),忘了出處),說(shuō)面向過(guò)程是蛋炒飯、面向?qū)ο笫巧w澆飯。
蛋炒飯混合在一起,蓋澆飯是分層的,如果不要蔥,蓋澆飯把上面的菜撥了直接換個(gè)沒(méi)蔥的菜,蛋炒飯就難搞了,得重新炒一份。
其實(shí)這個(gè)比喻體現(xiàn)的思想就是面向?qū)ο罂删S護(hù)性比較高,而且可以重用,更加靈活。
而面向過(guò)程就不易維護(hù),不易擴(kuò)展。
這個(gè)比喻沒(méi)錯(cuò),上面的說(shuō)法也沒(méi)錯(cuò),但是我覺(jué)得需要加個(gè)前提:在合適的場(chǎng)景。
雖說(shuō)我上面列了很多面向?qū)ο缶幊痰膬?yōu)點(diǎn),但是軟件設(shè)計(jì)沒(méi)有銀彈,沒(méi)有最好的,只有合適的。
當(dāng)你做一個(gè)很簡(jiǎn)單的玩意,比如簡(jiǎn)易計(jì)算器,你抽象來(lái)抽象去其實(shí)意義不大,直接按照面向過(guò)程的設(shè)計(jì)一條道走到底才是最合適的。
就像我們平日里面寫(xiě)代碼,是否遇到個(gè)情況:為了一個(gè)功能需要新建一個(gè)類(lèi),然后類(lèi)里面就一個(gè)方法。
因?yàn)榘凑彰嫦驅(qū)ο蟮乃季S,這個(gè)是需要抽象的。
然后為了復(fù)用還做了繼承、預(yù)留了一些接口等等,就想著以后擴(kuò)展。
可能過(guò)了很多年到這個(gè)項(xiàng)目撲街了,都沒(méi)擴(kuò)展上。
在項(xiàng)目里很多地方都做了這樣的鉤子,都白費(fèi),沒(méi)魚(yú)兒上鉤。
還不如當(dāng)時(shí)就直來(lái)直往的寫(xiě),繞來(lái)繞去的新同事進(jìn)來(lái)看的都一臉懵逼。
有些人說(shuō)代碼就是得這樣寫(xiě),就是要為了之后的擴(kuò)展,設(shè)計(jì)模式上!
捫心自問(wèn)一下,有多少之后用上了?
所以有很多大牛在那里罵:
“面向?qū)ο缶幊淌且粋€(gè)極其糟糕的主意,只有硅谷里的人能干出這種事情?!?— Edsger Dijkstra(圖靈獎(jiǎng)獲得者)
“有時(shí),優(yōu)雅的實(shí)現(xiàn)只需要一個(gè)函數(shù)。不是一個(gè)方法。不是一個(gè)類(lèi),不是一個(gè)框架。只是一個(gè)方法?!?— John Carmack(id Software的創(chuàng)始人、第一人稱(chēng)射擊游戲之父)
“面向?qū)ο缶幊陶Z(yǔ)言的問(wèn)題在于,它總是附帶著所有它需要的隱含環(huán)境。你想要一個(gè)香蕉,但得到的卻是一個(gè)大猩猩拿著香蕉,而其還有整個(gè)叢林?!?— Joe Armstrong(Erlang語(yǔ)言發(fā)明人)
還有挺多,我就不列出來(lái)了。
確實(shí)有時(shí)候?qū)懘a的時(shí)候能明顯感覺(jué)到有時(shí)候需要的只是一個(gè)函數(shù)。
所以對(duì)于那些:別問(wèn),問(wèn)就是面向?qū)ο?/span>,還有一些大肆鼓吹設(shè)計(jì)模式的:別問(wèn),問(wèn)就是設(shè)計(jì)模式 的人而言,我是不認(rèn)可的。
還是那句:
軟件設(shè)計(jì)沒(méi)有銀彈,沒(méi)有最好的,只有合適的。
復(fù)雜的業(yè)務(wù)用面向?qū)ο缶幊叹秃线m了嗎?
一般的說(shuō)法是面向?qū)ο筮m合復(fù)雜的場(chǎng)景,這句話其實(shí)也不全對(duì)。
當(dāng)時(shí)我在打 LOL 的時(shí)候就在感嘆,這技能的釋放,然后又因?yàn)榧恿?buf 可能有什么特別的計(jì)算,每一次版本變更好像改的東西挺多啊。
就像亞索出來(lái)的時(shí)候,這狂風(fēng)絕息斬對(duì)石頭人的大沒(méi)用,被蝎子拉了也沒(méi)用,這不是得做很多判斷啊。
每新出一個(gè)英雄,new 一個(gè)對(duì)象,其他英雄對(duì)象都得改啊,因?yàn)樾掠⑿坩槍?duì)不同英雄可能有不一樣的傷害效果。
總之我覺(jué)得很復(fù)雜,每一次改動(dòng)會(huì)涉及很多很多,所以腦子里面就有疑問(wèn)這是怎么做的,面向?qū)ο蟮脑挼酶暮枚嘌健?/p>
前幾天我看到了知乎 invalid s 的回答,給我解了惑。
原來(lái)復(fù)雜的業(yè)務(wù)用面向?qū)ο缶幊踢€真不一定合適。
他舉的是 WOW 的例子,雖說(shuō)我不知道 LOL 是不是這樣做的,但是這不重要。
他讓我知道在這個(gè)場(chǎng)景里面如果是以面向?qū)ο髞?lái)設(shè)計(jì),那面對(duì)如此繁多的職業(yè)、種族和技能,在頻繁地版本迭代下是招架不住的。
我截個(gè)圖,鏈接我放文末。
這種情況可以利用法術(shù)/技能數(shù)據(jù)庫(kù)化即表格化來(lái)解決。
所以從中可以看到不是面對(duì)復(fù)雜的場(chǎng)景就直接上面向?qū)ο蟮模?span style="font-weight: 600;color: rgb(60, 112, 198);">還是得具體情況具體分析,面向?qū)ο蟛皇侨f(wàn)能的。
最后
其實(shí)我還看到有人說(shuō)面向?qū)ο蟮谋举|(zhì)是對(duì)真實(shí)世界的映射,這還真不一定。
我們平日寫(xiě)代碼能很明顯地感受到有時(shí)候就是為了抽象和復(fù)用搞了一個(gè)類(lèi)。
而且很多情況抽象出來(lái)的類(lèi)和現(xiàn)實(shí)對(duì)應(yīng)不上,反正就是為了需求而造的一個(gè)類(lèi)。
網(wǎng)上也看到很多言論,說(shuō)啥 OOP 就是錯(cuò)的、或者說(shuō) OOP 就是對(duì)的。
我覺(jué)得都很極端,還是那句軟件設(shè)計(jì)沒(méi)有銀彈,沒(méi)有最好的,只有合適的。
關(guān)于面向?qū)ο筮€想提一下。
在去年我寫(xiě) Fork/Join 的時(shí)候提到了分而治之。
面向?qū)ο笃鋵?shí)也有這味道。
拿古代舉例,皇帝其實(shí)不知道具體治理細(xì)節(jié),也不用管理具體細(xì)節(jié),一個(gè)國(guó)家這么大的龐然大物抽象了很多事務(wù),分成了很多類(lèi)官員。
然后將每類(lèi)官員需要做的事情封裝好,讓每類(lèi)官員各司其職。
皇帝只需要統(tǒng)籌全局,根據(jù)每類(lèi)官員的職責(zé)頒發(fā)不同的任務(wù)即可,不需要關(guān)心他具體是如何實(shí)施的。
皇帝只要說(shuō),讓各縣都推行啥啥啥,即可。
其實(shí)等于只要招呼縣令這個(gè)類(lèi)去做事情,管你哪個(gè)縣,皇帝不需要關(guān)心。
然后每個(gè)縣令得到相同的命令,但是會(huì)有各自的治理方法,這其實(shí)就是多態(tài)。
這其實(shí)就是面向?qū)ο蟮乃枷搿?/p>
好了,說(shuō)了這么多不知道能不能講清什么叫面向?qū)ο?,如果不清晰的話還望見(jiàn)諒,畢竟能力有限。
我再稍微的總結(jié)一下面向?qū)ο缶幊蹋?/p>
OOP 其實(shí)就是一種編程范式或者說(shuō)風(fēng)格,是一種以類(lèi)或?qū)ο鬄閱卧獊?lái)組織代碼的編碼方式,讓代碼高內(nèi)聚,低耦合。
OO 符合人類(lèi)面對(duì)復(fù)雜事物時(shí)思考方式,抽象、建模、分類(lèi)、歸類(lèi)。
最后,歡迎加我好友進(jìn)行深入地交流,備注「進(jìn)群」,拉你進(jìn)交流&內(nèi)推群。
平日的面試題遇到難處,或者看某個(gè)知識(shí)點(diǎn)翻遍全網(wǎng)的資料還是感覺(jué)很模糊、不透徹,可以私聊我,給我留言。
遇到合適的我會(huì)整理寫(xiě)出一篇文章,不會(huì)的我去請(qǐng)教別人也給整出來(lái)。
那種工作遇到很細(xì)節(jié)的場(chǎng)景的還是別了,這種問(wèn)你上司比較合適:)
特別推薦一個(gè)分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒(méi)關(guān)注的小伙伴,可以長(zhǎng)按關(guān)注一下:
長(zhǎng)按訂閱更多精彩▼
如有收獲,點(diǎn)個(gè)在看,誠(chéng)摯感謝
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!