2020-05-17 面向對象編程的興衰

轉載自CSDN

不,面向對象編程(OOP)并沒有消亡。但它遠沒有以前那么流行了。

我記得當時在90年代,關于面向對象編程的教科書和計算機科學課程很多。當時那就是“風口”,下一波潮流。如果你沒有以那種方式編程,你就不是一個優(yōu)秀的程序員,或者至少是可悲地落后于時代發(fā)展潮流了。

當時,CS專業(yè)的學生以非常嚴格和教條化的方式學習OOP。從業(yè)者們不僅被鼓勵以對象和類的形式構建他們的應用程序,甚至被認為應該根據(jù)對象和類來考慮問題空間。這樣的做法被稱為“面向對象的分析和設計”。

然而,隨著時間的推進,人們開始意識到嚴格的面向對象方法會帶來許多問題。這些問題往往會使代碼復雜化,難以理解而且難以測試。

事實證明,OOP在某些問題領域確實比其他方法更出色。例如,OOP仍然是構建用戶界面(窗口和按鈕)的最自然的方式。但是,試圖使面向對象適應關系數(shù)據(jù)庫一直以來都簡直是一場災難。

以下是我所觀察到的一些問題:

“鴨嘴獸”效應

現(xiàn)實世界并不總是能被整潔地劃分為具有明確屬性定義的類(class)。

例如,假設你創(chuàng)建了一個代表動物王國的類層次結構。有爬行動物——冷血,有鱗片,卵生等等。還有哺乳動物——溫血,有絨毛,胎生。以及鳥類,兩棲動物,無脊椎動物等等

然后鴨嘴獸出現(xiàn)了,它似乎不適合你的任何類別。你該怎么做呢?

你是創(chuàng)建一個全新的類別呢,還是重新考慮整個分類方案?這兩種方法在工作量和程序復雜性方面都會產(chǎn)生巨大的成本。

深層次結構

我記得我在谷歌工作時,當時我們有一個JavaScript庫叫goog.ui,它被用于創(chuàng)建基于Web的用戶界面。以下是此庫中某個UI組件的繼承層次結構示例:

class ToolbarColorMenuButton* inheritsfrom ColorMenuButton?* inheritsfrom MenuButton?* inheritsfrom Button?* inheritsfrom Control?* inheritsfrom Component?* inheritsfrom EventTarget?* inheritsfrom Disposable?* inheritsfromObject

九個級別的類。太多了。

但情況會變得更糟。

許多高級類被只與少數(shù)子類相關的方法和屬性“污染”。例如,“Control”類有超過90種方法(method)。它具有設置狀態(tài)的方法,即使特定的子類是無狀態(tài)的; 它有添加和刪除子元素的方法,即使對control來說子元素沒有意義。

這種復雜性的一個重要原因是,該庫的作者試圖組織組件的不同方面——例如組件是按鈕還是滑塊,或者它是否有顏色——并通過將它們放入類的不同層次來實現(xiàn)這一點。

但實際上,這些不同方面彼此之間無關??Х缺羌t色的,和它是由陶瓷制成的,這是兩個獨立的特性。將紅色咖啡杯劃入“紅色物品”類別,還是將其放入“陶瓷制品”甚至“家居用品”類別中都是同樣正確的。任何一個選擇都是任意的,因為類別是由人頭腦中的反映和社會結構決定的。

在Google工作的最后幾年里,我創(chuàng)建了一個名為“Quantum Wiz”的新用戶界面工具包,旨在替代goog.ui。我們采用的規(guī)則之一(以典型的Google風格,以方程式編寫)是:

composition > inheritance

用直白的英語解釋的話,這說的是:

“更偏向于采用組合的思路——也就是說,能夠用更小的構建塊來組裝組件的功能——而不是繼承作為代碼重用的手段?!?/p>

因此,舉個栗子,如果按鈕有顏色,你將采用常規(guī)的“Button”對象(object)并向其添加“Color”方面(aspect),作為屬性或子對象,而不是創(chuàng)建一個新的“Color Button”類。

作為這項任務的結果,新工具包的類層次結構相對較淺,如果我沒記錯的話,只有兩三個級別。而且更容易理解和使用,也更強大。

(感謝Malte Ubl向我介紹了組合大于繼承的概念。)

對象不是真實的

Buckminster Fuller曾經(jīng)說過:“事物并不存在”。他的意思是,事物之間的區(qū)別主要由人的偏見導致。

例如,我坐的沙發(fā)是由分子力束縛在一起的原子的集合。然而,這些原子也會受到房間內(nèi)其他物體的影響——地毯,茶幾,甚至是房間內(nèi)的空氣。沙發(fā)本身由各種部件組成——織物,木材,金屬彈簧等等——它們也受到分子力的約束。那么沙發(fā)是一個對象,還是很多對象?也許世上只有一個對象——即我們所在的這個宇宙。

因為人類的視覺和觸覺在很大程度上只對表面屬性有響應——比如顏色和質(zhì)感——我們傾向于基于表面現(xiàn)象對世界進行分類。相反,想象一下,如果我們能夠直接感知周圍物體內(nèi)的分子組成。我們可能會看到一個“銅”對象,代表著房屋中的所有布線和管道,一個“氮”對象,代表著房間的氣體,一個“水”對象,一個“木頭”對象,等等。

Fuller的觀點是,我們將世界“解析”為離散的“事物”的能力是任意的,這更多地反映了我們的人類心理而不是物理現(xiàn)實。

因為面向對象的繼承涉及將事物組織成類,所以它不能很好地模擬現(xiàn)實世界; 但它能很好地模擬人類思考現(xiàn)實世界的方式。

方法(method)也不真實

我記得大約二十年前的一段小插曲,一位軟件供應商的技術代表試圖向我司的工程人員解釋OOP。他試圖爭辯說,面向對象是一種模擬現(xiàn)實世界的方式,他給出的例子就像上面說的咖啡杯那種。他說杯子可能有個“drink()”的方法。

我記得的是,我對此有一個非常強烈的反應——我認為他所說的完全是胡說八道。除了它的特定目的之外,一個物理對象可以有許多用途。我可以用咖啡杯作為鎮(zhèn)紙或門擋; 這是否意味著它有一個“holdDownPapers()”或“keepDoorOpen()”方法?我可以將它用作武器,玩具或藝術品。我甚至可以將杯子碎成碎片,或將其研磨成粉末,并以創(chuàng)造性的方式使用其殘余物。

內(nèi)部邏輯與外部邏輯

嚴格的OOP風格的一個原則是,永遠不可能從外部改變對象的內(nèi)部狀態(tài)。任何改變對象狀態(tài)的業(yè)務或應用程序邏輯都必須作為對象本身的方法實現(xiàn)。因此,舉個栗子,如果要刪除文本字段中的所有文本,則不能簡單地進行:

textField.value = ""; // Settoemptystring

這將違背規(guī)則。相反,你不得不這樣:

textField.clear(); // Clear the content of the field

對于簡單的操作,這沒關系。但是這很容易過火,特別是正在進行的操作處理的是不同對象之間的復雜關系時。

有時候,如果算法獨立于任何對象之外,反而更好。這真的是一個重要的問題:對于這個問題集,你更關心名詞還是動詞?

下面這是一個具體的例子:最近我開始研究編譯器(編寫編譯器是我的一個愛好; 在我做游戲開發(fā)的時候,我發(fā)明了許多腳本語言)。在過去,當我編寫編譯器時,我會采用非常嚴格的OOP方法來設計內(nèi)部數(shù)據(jù)結構。有各種類層次結構表示抽象語法樹,表達式圖,類型等。

通常,編譯器通過一系列階段或“傳遞”來處理這些數(shù)據(jù)結構,每一步的輸出被送到下一步的輸入中。

在過去,我傾向于按照推薦的OOP樣式為每個操作中的每個對象設置一些邏輯。這帶來不好的后果,當我添加更多步驟時,對象變得越來越復雜。

更糟糕的是,這使得給這些對象寫單元測試異常困難。這些復雜的對象在被創(chuàng)建出來之前就需要大量的基礎結構。這意味著為了測試一個對象,我必須創(chuàng)建大量的腳手架來滿足所有先決條件。

結果,我的測試覆蓋率往往很差,因為編寫測試是一項耗費大量精力的工作。

最近,我采取了另一種方法。在我最新的編譯器中,所有這些內(nèi)部數(shù)據(jù)結構都是“傻瓜型”的,意思是說它們所做的只是保存數(shù)據(jù)而已,沒有別的。用于操作和轉換對象的所有代碼都在這些對象的外部。

這對代碼的組織有很大的好處。每個算法都集中在一個地方,而不是分散在一堆源文件中。當我想測試一個特定的編譯器操作時,我可以輕松地創(chuàng)建一些示例對象并將其提供給該操作。因此,我的測試寫起來更容易了,所以我就能寫更多的測試了,從而就能有比以前更好的測試覆蓋率了。

關系數(shù)據(jù)庫

前面我提到過,以面向對象的方式處理關系數(shù)據(jù)庫會有問題。對象關系映射(ORM)被一位評論家稱為計算機科學領域的越戰(zhàn)。(警告——那篇文章很長,很深奧,而且有點傾向性。)

我的大致感覺是,在處理大數(shù)據(jù)時,你不應將你的記錄視為“對象”。關系數(shù)據(jù)庫非常強大,但它們提供的強大功能并不是非?!邦愃茖ο蟆?。我傾向于認為關系數(shù)據(jù)流更像流體,你可以使用代數(shù)運算的方式來劃分,轉換和組合數(shù)據(jù)。

函數(shù)式編程

在過去十年左右的時間里,人們越來越關注函數(shù)式編程(FP)。與OOP一樣,函數(shù)式編程不僅僅是單純的一件事物,而是一套整體的風格上的原則。然而,它的要點是,雖然OOP專注于與對象進行交互或通信,但在FP中,重點在于對它們的轉換。這里的“轉換”,意思是你獲取一些對象,將它傳遞給一個函數(shù),結果是一個全新的對象,代表著對輸入內(nèi)容所做的一些轉換。原始對象要么被保留,要么被丟棄,但不會以任何方式被更改或修飾。

在我自己的編程過程中,我更喜歡“混合”方法,在某些地方使用FP技術,而在其他地方使用OOP技術。在解決某些問題上FP確實能大放異彩,但也有另一些問題上OOP是更明智的選擇。

我知道很多FP愛好者都熱衷于“純粹”的函數(shù)式語言,其中所有對象都是不可變的,并且只能被轉換,而不能被修改。然而,我發(fā)現(xiàn)純粹的方法傾向于把某些相對簡單的編程實踐變成謎題。我的意思是一些聰明的,但不那么顯而易見的技巧,這能吸引那種喜歡腦筋急轉彎的人,但是對其他人來說卻是完全無法理解的。

因此,我傾向于在合理的范圍內(nèi)使用FP,使閱讀我代碼的普通程序員都能理解。如果我想做任何抖機靈的事情,我會寫一篇長篇評論來解釋我所做的事情,以及它是如何work的(這滿足了我炫耀的心理——我經(jīng)常認為編程應該是一種表演藝術。)

所以,面向對象編程不再有昔日的輝煌了。它仍然是一個很好的工具,仍然值得學習。但它已跌下神壇,你很難再看到有人能像 25 年前那樣,以宗教般的狂熱來吹捧它了。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容