微信搜 「yes的練級攻略」乾貨滿滿,不然來掐我,回覆【123】一份20W字的演算法刷題筆記等你來領。 個人文章彙總:https://github.com/yessimida/yes 歡迎 star !
你好,我是 yes。
物件導向程式設計想必大家都耳熟能詳,但是寫了這麼多程式碼你對物件導向有清晰的認識嗎?
來看看這幾個問題:
-
到底什麼是物件導向程式設計?
-
和麵向過程程式設計有什麼區別?
-
什麼又稱為面嚮物件語言、程式導向語言?
-
用面嚮物件語言寫的程式碼就物件導向了?
-
物件導向程式設計真的就這麼好嗎?
-
複雜的業務用物件導向程式設計就合適了嗎?
我還真沒具體地定義過到底什麼是物件導向程式設計。
所以假設有人問到底什麼是物件導向程式設計?有什麼好處?
一時還真不知道怎麼說,或者說成體系的解釋。
這篇文章我就談談我的理解,也試著看能不能說清啥叫物件導向程式設計。
正文
從二進位制命令到組合語言。
從組合語言到程式導向語言再到面嚮物件語言。
計算機語言的發展是為了便於人類的使用,使其更符合人類的思考方式。
計算機的思路就是取指執行,一條直道走到底,它可不會管你什麼抽象,不管什麼業務建模,通通得給它變成一條條指令,排好順序讓它執行。
而我們人類不一樣,我們的思維在簡單場景來看是一條道,但在複雜場景就需要做各種分類,才能理清楚關係,處理好事務。
就像法庭,分為法官、書記員、法警、原告、被告、證人等角色。
這麼多人分好類,按照法庭審理各司其職,一個案子才能高效、順利得審判。
再回到計算機語言來,彙編我就不說了,程式導向其實就是一條道的思路,因為起初就是按計算機的思路來編寫程式。
我就拿用咖啡機煮咖啡為例,按照程式導向的流程是:
-
執行加咖啡豆方法
-
執行加水方法
-
執行煮咖啡方法
-
執行喝咖啡方法
很簡單直觀的操作,你可能沒什麼感覺,我再按物件導向思想來分析下這個流程。
在執行煮咖啡操作前要抽象出:人和咖啡機(分類),然後開始執行:
-
人.加咖啡豆
-
人.加水
-
咖啡機.煮
-
人.喝咖啡
是不是有點感覺了?
程式導向,從名字可以得知重點是過程,而物件導向的重點是物件。
從這個例子可以看出兩者的不同:程式導向是很直接的思維,一步步的執行,一條道走到底。
而物件導向是先抽象,把事物分類得到不同的類,劃分每個類的職責,暴露出每個類所能執行的動作,然後按邏輯執行時呼叫每個類的方法即可,不關心內部的邏輯。
從例子可以看出物件導向程式設計執行的步驟沒有變少,整體執行流程還是一樣的,都是先加咖啡豆、加水、煮咖啡、喝,這個邏輯沒有變。
無非就是劃分了類,把每一步驟具體的實現封裝了起來,散佈在不同的類中。
對我們程式設計師來說是最最直接的感受:變的其實就是程式碼的分佈,煮咖啡的程式碼實現被封裝在咖啡機內部,喝咖啡的程式碼實現被封裝在人內部,而不是在一個方法中寫出來。
程式碼的分佈確實是最直觀的,但是變得不僅只是分佈,而是思想上的變化。
就是上面提到的計算機思維到人類思維的變化。
我認為這個變化是因為軟體的發展,業務越來越複雜。
人們用程式導向語言編寫複雜的軟體時,需要按照不同的功能把一些資料和函式放到不同的檔案中,漸漸地人們就發現這不就是先分類嗎?
並且好像業務分析下來都能和現實世界的東西對應上?
於是人們慢慢地總結、提煉就演變成了物件導向,再根據物件導向的特性提煉出關鍵點:封裝、繼承和多型。
而這個物件導向思想就類似我們人類面對複雜場景時候的分析思維:歸類、彙總。
所以物件導向程式設計就成為了現在主流的程式設計風格,因為符合人類的思考方式。
程式導向程式設計和麵向物件程式設計從思想上的變化是:從計算機思維轉變成了人類的思維來編寫編碼。
所以我們知道物件導向程式設計其實是一種進步,一種更貼近人類思考方式的編碼風格,是源於人們用程式導向程式設計時的經驗總結。
至此我們知道了物件導向程式設計的來源,相信知曉了來源能更好的理解物件導向。
那到底什麼是物件導向程式設計?
物件導向程式設計(Object Oriented Programming,OOP)是一種程式設計正規化或者說程式設計風格。
學術一點講就是把類或物件作為基本單元來組織程式碼,並且運用提煉出的:封裝、繼承和多型來作為程式碼設計指導。
這其實就是物件導向程式設計。
其實從上面煮咖啡的流程應該能 get 到這個含義了。
OOP 說白了就是拿到需求開始分析,進行抽象建立業務模型,每個模型建立對應的類。
思考業務的互動,根據互動定義好介面並做好介面的控制訪問,將於此類相關的資料和動作都封裝起來。
抽象出父類,子類繼承父類來進行程式碼的複用和擴充套件。
執行功能時用父類來呼叫,在實際程式碼執行過程會進行動態繫結,呼叫子類的實現達到多型的特性。
多型,學術點講就是:執行時用相同的程式碼根據不同型別的例項呈現出不同行為的現象。
如果有新功能要實現,只需要建立一個新子類,以前的執行邏輯不需要發生變化,這就是「開閉原則」,對修改關閉,對擴充套件開放”。
來簡單的看個程式碼可能會有更直觀的感受,沒記錯的話大學時也是拿動物舉例。
狗是動物、鴨子是動物,所以有個 Animal 類。
然後能發聲,所以有 voice 方法。
public class Animal { public void voice(){ System.out.println("動物的叫聲"); } }
然後搞個 Dog、Duck 繼承 Animal 實現各自的 voice。
public class Dog extends Animal { public void voice(){ System.out.println("汪汪汪~"); } } public class Duck extends Animal { public void voice(){ System.out.println("gagaga~"); } }
然後到時候就可以例項化不同的物件來達到多型的效果。
public class Test{ private Animal animal; public void setAnimal(Animal animal) { this.animal = animal; } public void voice(){ animal.voice(); } }
多型帶來的好處,無非就是 Test 裡面程式碼不用動,你想要狗叫你就 new Dog 然後 set 進去,如果要鴨子就 new Duck 然後 set 進去。
如果加入了新動物那就建一個新動物類 set 進去就行,符合開閉原則。
和麵向過程程式設計有什麼區別?
其實從上面煮咖啡和動物的這兩個例子應該能感受出來區別。
最重要的是思想上的區別,上面也已經提到了。
還有一點就是資料和動作。
程式導向程式設計這種程式設計風格是以過程作為基本單元來組織程式碼的,過程其實就是動作,對應到程式碼中來就是函式,程式導向中函式和資料是分離的,資料其實就是成員變數。
而物件導向程式設計的類中資料和動作是在一起的,這也是兩者的一個顯著的區別。
什麼又稱為面嚮物件語言、程式導向語言
面嚮物件語言其實就是有現成的語法機制來支援類、物件的語言,比如 Java。
當然還要有支援繼承、多型的語法機制。
程式導向語言就反著理解,沒有現成的語法機制來支援類、物件等基本單元來組織程式碼。
當然不是你用了面嚮物件語言寫出來的程式碼就物件導向了。
你要通篇就一個 class,一堆雜亂無章都往裡面塞,不歸類、沒有封裝的意識,一條直到,這可不叫物件導向程式設計。
當然也不是用程式導向語言就寫不出物件導向的程式碼,只是由於語法層面的不支援,寫起來沒那麼方便,需要用一些手段,具體就不展開了。
所以語言只是為了更好的支援程式設計正規化,重要的還是思想上的轉變。
物件導向程式設計真的就這麼好嗎?
結論先上:軟體設計沒有銀彈,沒有最好的,只有合適的。
前面也提到了物件導向更符合人類的思考方式,這其實就是優勢,能 hold 住複雜的需求。
複雜的需求關係都是錯綜複雜的,我們分類、抽象、封裝就能得到一個個規範化的模組(類)。
大型專案都需要很多人協同合作,因為劃分的清晰,每個人只要實現自己負責的模組。
然後根據模組之間關係再組裝起來即可。
脈絡清晰也使得我們開發的時候思路也異常的清晰,提升開發的效率。
並且由於封裝的特性,類的內部是高度內聚的,會利用訪問控制許可權暴露出有限的訪問,這使得類內部的資料不會被隨意更改,提高程式碼的維護性。
還有前面提到的繼承,提高程式碼的複用性,由繼承實現的多型也符合開閉原則。
我還看過一個很形象的解釋(很久之前看過,忘了出處),說程式導向是蛋炒飯、物件導向是蓋澆飯。
蛋炒飯混合在一起,蓋澆飯是分層的,如果不要蔥,蓋澆飯把上面的菜撥了直接換個沒蔥的菜,蛋炒飯就難搞了,得重新炒一份。
其實這個比喻體現的思想就是物件導向可維護性比較高,而且可以重用,更加靈活。
而程式導向就不易維護,不易擴充套件。
這個比喻沒錯,上面的說法也沒錯,但是我覺得需要加個前提:在合適的場景。
雖說我上面列了很多物件導向程式設計的優點,但是軟體設計沒有銀彈,沒有最好的,只有合適的。
當你做一個很簡單的玩意,比如簡易計算器,你抽象來抽象去其實意義不大,直接按照程式導向的設計一條道走到底才是最合適的。
就像我們平日裡面寫程式碼,是否遇到個情況:為了一個功能需要新建一個類,然後類裡面就一個方法。
因為按照物件導向的思維,這個是需要抽象的。
然後為了複用還做了繼承、預留了一些介面等等,就想著以後擴充套件。
可能過了很多年到這個專案撲街了,都沒擴充套件上。
在專案裡很多地方都做了這樣的鉤子,都白費,沒魚兒上鉤。
還不如當時就直來直往的寫,繞來繞去的新同事進來看的都一臉懵逼。
有些人說程式碼就是得這樣寫,就是要為了之後的擴充套件,設計模式上!
捫心自問一下,有多少之後用上了?
所以有很多大牛在那裡罵:
“物件導向程式設計是一個極其糟糕的主意,只有矽谷裡的人能幹出這種事情。” — Edsger Dijkstra(圖靈獎獲得者)
“有時,優雅的實現只需要一個函式。不是一個方法。不是一個類,不是一個框架。只是一個方法。” — John Carmack(id Software的創始人、第一人稱射擊遊戲之父)
“物件導向程式語言的問題在於,它總是附帶著所有它需要的隱含環境。你想要一個香蕉,但得到的卻是一個大猩猩拿著香蕉,而其還有整個叢林。” — Joe Armstrong(Erlang語言發明人)
還有挺多,我就不列出來了。
確實有時候寫程式碼的時候能明顯感覺到有時候需要的只是一個函式。
所以對於那些:別問,問就是物件導向,還有一些大肆鼓吹設計模式的:別問,問就是設計模式 的人而言,我是不認可的。
還是那句:
軟體設計沒有銀彈,沒有最好的,只有合適的。
複雜的業務用物件導向程式設計就合適了嗎?
一般的說法是物件導向適合複雜的場景,這句話其實也不全對。
當時我在打 LOL 的時候就在感嘆,這技能的釋放,然後又因為加了 buf 可能有什麼特別的計算,每一次版本變更好像改的東西挺多啊。
就像亞索出來的時候,這狂風絕息斬對石頭人的大沒用,被蠍子拉了也沒用,這不是得做很多判斷啊。
每新出一個英雄,new 一個物件,其他英雄物件都得改啊,因為新英雄針對不同英雄可能有不一樣的傷害效果。
總之我覺得很複雜,每一次改動會涉及很多很多,所以腦子裡面就有疑問這是怎麼做的,物件導向的話得改好多呀。
前幾天我看到了知乎 invalid s 的回答,給我解了惑。
原來複雜的業務用物件導向程式設計還真不一定合適。
他舉的是 WOW 的例子,雖說我不知道 LOL 是不是這樣做的,但是這不重要。
他讓我知道在這個場景裡面如果是以物件導向來設計,那面對如此繁多的職業、種族和技能,在頻繁地版本迭代下是招架不住的。
我截個圖,連結我放文末。
這種情況可以利用法術/技能資料庫化即表格化來解決。
所以從中可以看到不是面對複雜的場景就直接上物件導向的,還是得具體情況具體分析,物件導向不是萬能的。
最後
其實我還看到有人說物件導向的本質是對真實世界的對映,這還真不一定。
我們平日寫程式碼能很明顯地感受到有時候就是為了抽象和複用搞了一個類。
而且很多情況抽象出來的類和現實對應不上,反正就是為了需求而造的一個類。
網上也看到很多言論,說啥 OOP 就是錯的、或者說 OOP 就是對的。
我覺得都很極端,還是那句軟體設計沒有銀彈,沒有最好的,只有合適的。
關於物件導向還想提一下。
在去年我寫 Fork/Join 的時候提到了分而治之。
物件導向其實也有這味道。
拿古代舉例,皇帝其實不知道具體治理細節,也不用管理具體細節,一個國家這麼大的龐然大物抽象了很多事務,分成了很多類官員。
然後將每類官員需要做的事情封裝好,讓每類官員各司其職。
皇帝只需要統籌全域性,根據每類官員的職責頒發不同的任務即可,不需要關心他具體是如何實施的。
皇帝只要說,讓各縣都推行啥啥啥,即可。
其實等於只要招呼縣令這個類去做事情,管你哪個縣,皇帝不需要關心。
然後每個縣令得到相同的命令,但是會有各自的治理方法,這其實就是多型。
這其實就是物件導向的思想。
好了,說了這麼多不知道能不能講清什麼叫物件導向,如果不清晰的話還望見諒,畢竟能力有限。
我再稍微的總結一下物件導向程式設計:
OOP 其實就是一種程式設計正規化或者說風格,是一種以類或物件為單元來組織程式碼的編碼方式,讓程式碼高內聚,低耦合。
OO 符合人類面對複雜事物時思考方式,抽象、建模、分類、歸類。
最後,歡迎加我好友進行深入地交流,備註「進群」,拉你進交流&內推群。
平日的面試題遇到難處,或者看某個知識點翻遍全網的資料還是感覺很模糊、不透徹,可以私聊我,給我留言。
遇到合適的我會整理寫出一篇文章,我不會的去請教別人也給整出來。
那種工作遇到很細節的場景的還是別了,這種問你上司比較合適:)
歡迎關注我的公眾號【yes的練級攻略】,更多硬核文章等你來讀。
微信搜尋【yes的練級攻略】,關注 yes,回覆【123】一份20W字的演算法刷題筆記等你來領,從一點點到億點點,我們下篇見。 個人文章彙總:https://github.com/yessimida/yes 歡迎 star !
巨人的肩膀
https://www.cnblogs.com/hdu-2010/p/3778515.html
https://www.zhihu.com/question/20275578/answer/26577791