工作這麼多年,你能向新人解釋清到底什麼是面向對象編程嗎?

微信搜 「yes的練級攻略」乾貨滿滿,不然來掐我,回復【123】一份20W字的演算法刷題筆記等你來領。 個人文章匯總://github.com/yessimida/yes 歡迎 star !

你好,我是 yes。

面向對象編程想必大家都耳熟能詳,但是寫了這麼多程式碼你對面向對象有清晰的認識嗎?

來看看這幾個問題:

  • 到底什麼是面向對象編程?

  • 和面向過程編程有什麼區別?

  • 什麼又稱為面向對象語言、面向過程語言?

  • 用面向對象語言寫的程式碼就面向對象了?

  • 面向對象編程真的就這麼好嗎?

  • 複雜的業務用面向對象編程就合適了嗎?

我還真沒具體地定義過到底什麼是面向對象編程。

所以假設有人問到底什麼是面向對象編程?有什麼好處?

一時還真不知道怎麼說,或者說成體系的解釋。

這篇文章我就談談我的理解,也試著看能不能說清啥叫面向對象編程。

正文

從二進位命令到彙編語言。

從彙編語言到面向過程語言再到面向對象語言。

電腦語言的發展是為了便於人類的使用,使其更符合人類的思考方式。

電腦的思路就是取指執行,一條直道走到底,它可不會管你什麼抽象,不管什麼業務建模,通通得給它變成一條條指令,排好順序讓它執行。

而我們人類不一樣,我們的思維在簡單場景來看是一條道,但在複雜場景就需要做各種分類,才能理清楚關係,處理好事務。

就像法庭,分為法官、書記員、法警、原告、被告、證人等角色。

這麼多人分好類,按照法庭審理各司其職,一個案子才能高效、順利得審判。

再回到電腦語言來,彙編我就不說了,面向過程其實就是一條道的思路,因為起初就是按電腦的思路來編寫程式。

我就拿用咖啡機煮咖啡為例,按照面向過程的流程是:

  1. 執行加咖啡豆方法

  2. 執行加水方法

  3. 執行煮咖啡方法

  4. 執行喝咖啡方法

很簡單直觀的操作,你可能沒什麼感覺,我再按面向對象思想來分析下這個流程。

在執行煮咖啡操作前要抽象出:人和咖啡機(分類),然後開始執行:

  1. 人.加咖啡豆

  2. 人.加水

  3. 咖啡機.煮

  4. 人.喝咖啡

是不是有點感覺了?

面向過程,從名字可以得知重點是過程,而面向對象的重點是對象。

從這個例子可以看出兩者的不同:面向過程是很直接的思維,一步步的執行,一條道走到底。

而面向對象是先抽象,把事物分類得到不同的類,劃分每個類的職責,暴露出每個類所能執行的動作,然後按邏輯執行時調用每個類的方法即可,不關心內部的邏輯。

從例子可以看出面向對象編程執行的步驟沒有變少,整體執行流程還是一樣的,都是先加咖啡豆、加水、煮咖啡、喝,這個邏輯沒有變。

無非就是劃分了類,把每一步驟具體的實現封裝了起來,散布在不同的類中。

對我們程式設計師來說是最最直接的感受:變的其實就是程式碼的分布,煮咖啡的程式碼實現被封裝在咖啡機內部,喝咖啡的程式碼實現被封裝在人內部,而不是在一個方法中寫出來。

程式碼的分布確實是最直觀的,但是變得不僅只是分布,而是思想上的變化。

就是上面提到的電腦思維到人類思維的變化。

我認為這個變化是因為軟體的發展,業務越來越複雜。

人們用面向過程語言編寫複雜的軟體時,需要按照不同的功能把一些數據和函數放到不同的文件中,漸漸地人們就發現這不就是先分類嗎?

並且好像業務分析下來都能和現實世界的東西對應上?

於是人們慢慢地總結、提煉就演變成了面向對象,再根據面向對象的特性提煉出關鍵點:封裝、繼承和多態。

而這個面向對象思想就類似我們人類面對複雜場景時候的分析思維:歸類、匯總。

所以面向對象編程就成為了現在主流的編程風格,因為符合人類的思考方式。

面向過程編程和面向對象編程從思想上的變化是:從電腦思維轉變成了人類的思維來編寫編碼。

所以我們知道面向對象編程其實是一種進步,一種更貼近人類思考方式的編碼風格,是源於人們用面向過程編程時的經驗總結。

至此我們知道了面向對象編程的來源,相信知曉了來源能更好的理解面向對象。

那到底什麼是面向對象編程?

面向對象編程(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字的演算法刷題筆記等你來領,從一點點到億點點,我們下篇見。 個人文章匯總://github.com/yessimida/yes 歡迎 star !

巨人的肩膀

//www.cnblogs.com/hdu-2010/p/3778515.html
//www.zhihu.com/question/20275578/answer/26577791