怎樣理解或評價面向對象的編程技術?
卡內基梅隆大學教授Robert Harper在博客上稱,
該校計算機科學系從大一新生課程中刪除了面向對象編程。他說,面向對象編程從入門課程中完全取消了,因為O-O的性質是反模塊化和反並行的,不適合進入現代計算機科學教學課程。 原文地址:http://existentialtype.wordpress.com/2011/03/15/teaching-fp-to-freshmen/所以,我想知道大家是怎麼理解或評價面向對象編程技術的,
還有怎麼理解他說的O-O的性質是反模塊化和反並行的。
又是矯枉過正的改革。以前從面向過程改成面向對象的時候也是這樣。學術界似乎就沒有不這樣一東一西的時候……
面向對象始於模擬應用,後來被視為面向過程編程無法向巨型項目擴展絕症的解藥。再以後被『發揮』到極致,不管適不適合都要用面向對象的方式去解決,應了那句老話『鎚子眼裡全是釘子』。基本上代碼裡面出現諸如 Executor.execute() 類似表達時,它在面向對象這條歧途上就已經走得太遠了。當所有的工具、方法論、指導思想都是同一種角度出發的時候,產生這樣怪異的結果也就不難理解了。
面向對象的核心是封裝狀態和相應的過程。通常面向對象是通過改變內部狀態實現最終目的。調用對象過程的主要目的是產生改變其內部狀態這個副作用(side effect)。這樣封裝的初衷是避免多個主體訪問、修改同一狀態造成混亂。在不少場合這樣的封裝確實也達到了目的,所以面向對象的方式才這麼普及。
但可變狀態並不適合多核並行計算。如果多個並行進程需要用到同一狀態,對這個狀態的修改會導致一致性問題:由於訪問、修改的先後順序不同,各個進程也許會看到不同的結果。鎖機制能部分的避免一致性問題,但並不解決多核並行的性能問題。不變狀態則不存在這個問題,多個並行進程可以隨意訪問某個狀態。因為狀態是不變的,所以不存在一致性的問題。
此外,面向對象的『過程』部分還是原來面向過程的『過程』,其命令是有順序的。可變狀態的副作用使得命令之間的存在順序依賴。聰明的編譯器會對過程的順序進行分析,看看哪些是可以重新排序或者同時執行的,但由於依賴關係沒有指明,這樣的優化還是相當有限。這導致了程序可以並行化的部分不明朗。順序依賴也不利於多核並行計算。
函數式編程能避免這些問題(雖然這樣說只是部分正確)。純粹的函數式語言(如 Haskell)是杜絕可變狀態的。換句話說,純粹的函數式語言里,沒有特別聲明的部分都可以安全的默認為無副作用。無副作用的好處是在很多場合下,多個函數之間不存在順序依賴,計算順序可以隨意調換。存在順序依賴的部分則需要通過特別的構造(如 Haskell 的 Monad)顯式進行。不變狀態+無順序依賴使得程序能夠被有效並行計算部分的比例加大了,因而說純粹的函數式編程有利於並行。然而不純粹的函數式語言就沒有這樣的優點了,剩下的更多的反而是基於函數組織代碼的思想。
當然我們都知道複雜的現實世界是不會那麼美好可以只用面向對象或者只用函數式的。於是現在開始流行所謂的混合對象函數式編程(Object-Functional Programming, OFP)。OFP 的典型代表是 Scala。在 Scala 里,面向對象和函數式編程被融合在了一起,函數即對象,而鼓勵用函數式操作對象。此外,Scala 同時支持可變狀態和不變狀態,並且提倡用面向對象的方法構造不變狀態,然後用函數式方法進行處理,可以說揚了二者之長、避了二者之短。唯一問題是,這樣的混合模式無法做到純粹。不知道內部實現的狀況下,是無法知道某個對象的過程是否具有副作用、命令順序是否可以調換的。通常這種情況下只能假設有副作用、順序不可調換。於是就成了一個用起來稍微方便一些的面向對象語言,而喪失了純粹函數式的核心優點。
這樣說起來挺杯具,但總結起來就是:沒有什麼是萬能的。根據實際情況選擇合適、方便的工具。
更新:在 @馮東 的提醒下,補充一點。目前廣泛應用的並行計算模式之一其實是依靠分割大量數據,然後在眾多個獨立進程里處理。沒錯,這就是通用圖形處理器計算(GPGPU)。這樣的並行模式中,數據集分割後,在每個獨立進程之間不能互相修改狀態,但進程內部是可以有可變狀態的。
簡單地說,就是UI離不了,其它應用碰不得。巧合的是,大多數UI framework都是嚴格單線程的,比如Cocoa、Swing。Win32是one message queue per thread,其實只是一種複雜化的或者說多實例化的單線程模型,而且實際中很少有人真的在non-main-thread里開闢message queue。這也和『反並行化』相互印證了。
另外,當你試圖運用OOP的時候,就會傾向於擴大單個模塊的複雜度,忽視進程分離等降低複雜度的技術。這個問題太複雜了。舉個最小化的例子來說明問題:比如一個程序中有一個變數。這個變數代表的是什麼含義?最寬泛的說,它是這個程序的輸入的歷史(時間變化看作外部事件),為什麼要使用這個變數?因為他會影響未來的行為。如果不考慮計算能力的問題,假定計算能力都是無窮大,任何計算都可以瞬間完成,那麼就可以忠實記錄所有的輸入歷史,每次需要計算的時候完全的重新計算,這樣,程序的變數(對象、態)之間就沒有耦合,即邏輯都是獨立的,也完全沒有並發問題。愛怎麼並發怎麼並發,因為沒有態存在。
OO模型定義了兩個程序要素,行為(方法)和實體。行為實際上是態之間的dependency,而態和態之間的依賴性決定了行為的發生順序需要符合一定規則。程序的錯誤發生在沒有正確的處理態之間的依賴性,這雖然不能說定義態是錯誤的,但事實上它讓程序容易發生錯誤,比如一個對象拋出的事件,從對象內部看就萬事大吉了,但是你讓框架怎麼處理事件的順序?是否要依賴對象內部的邏輯,對象和對象之間的依賴性?雖然去耦合是開發者常說的一句話,但是在一些業務邏輯上,對象之間的contract, dependency和constraint是客觀存在。Side-Effect問題不是一個小問題。
這個教授提倡的是functional programming;它在分散式計算中流行,尤其是實時和嵌入式的,比如通訊;從這些很抽象的層面——同時也是離實際創建的程序很遠的角度說,它說基於State的Programming Paradigm有Side Effect,反模塊化反並行,都是成立的。但是在編程實踐中,OO直覺,開發效率高,工具、工業框架都成熟,並不能說它是有原罪的。
對OO來說,最好只是把它看作一種語言工具,而不是模型工具;事實上我們缺乏General Purpose,廣泛適用各種開發需求,兼顧開發效率和邏輯正確性的模型開發工具;在目前階段,可以多了解一些Design Pattern,狀態機使用,Model-Driven Development,Model Checking(比如SPIN,或者B-method),尤其是有限狀態機和形式化方法的知識。設計階段可以從粗粒度上考慮嚴格的邏輯,到細粒度的具體實現還是得依賴OO語言。大多數情況下你不可能把程序代碼都寫成狀態機,它將幾十倍的增加代碼量,而且非常不靈活,難以應對設計變更。
但是說fp會替代oo語言我也覺得這是過於樂觀的估計,個人覺得未來的開發應該是疊層的,最底層仍然是oo語言,之上是模型驅動的開發,代碼生成技術,模型驗證技術(很大程度上替代測試)。
My 2 Cents.支持「UI離不了,其它應用碰不得」的說法。雖然碰不得可能有些絕對,不過從當前的常見應用來看,確實如此。
不過我不太贊成取消OO的教學。無論如何,相對於FP等模式,OO依然是最易理解和學習,而且大部分情況下掌握他也會有好處。從就業上來看,要求擁有面向對象的理解和設計思維的崗位也是大多數。
但是如果想獲得一個高端的職位,成為一個比較「牛」的程序員,僅僅掌握OO顯然是不夠的。那些年搞不懂的高深術語——依賴倒置?控制反轉?依賴注入?面向介面編程
那些年,空氣中彷彿還能聞到漢唐盛世的餘韻,因此你決不允許自己的臉上有油光,時刻保持活力。然而,你一定曾為這些「高深術語」感到過困擾。也許時至今日,你仍對它們一知半解。不過就在今天,這一切都將徹底改變!我將帶領你以一種全新的高清視角進入奇妙的編程世界,領略涵泳在這些「高深術語」中的活潑潑的地氣,以及翩躚於青萍之末的雲水禪心。
·內聚內聚,通俗的來講,就是自己的東西自己保管,自己的事情自己做。
經典理論告訴我們,程序的兩大要素:一個是數據(data),一個是操作(opration)。而 PASCAL之父Nicklaus Wirth則進一步提出了「程序 = 數據結構 + 演算法」的著名公式。雖然提法上有所差異,但是其根本內涵卻是一致的,微妙的差別在於,「數據 + 操作」是微觀的視域,「數據結構 + 演算法」則是中觀的視域。而在宏觀的視域下,我認為「程序 = 對象 + 消息」。對象是什麼?對象就是保管好自己的東西,做好自己的事情的程序模塊——這就是內聚!傳統的面向過程編程方法由於割裂了數據結構和演算法,使得軟體的內聚性普遍低迷,曾一度引發了軟體危機。試想,大家都自己的東西不好好保管,自己的事情也不好好做,不引發危機才怪呢!當然,對象的內聚只是內聚的一個層次,在不同的尺度下其實都有內聚的要求,比如方法也要講內聚,架構也要講內聚。
《周易·彖傳》中講「乾道變化,各正性命,保合太和,乃利貞」,就是要求每一個個體因循著各自的稟賦而努力成就各自的品性,然後各自保全,彼此和合,最終達成宇宙的完滿狀態。《論語·憲問》中,子路問君子。子曰:「修己以敬。」曰:「如斯而已乎?」曰:「修己以安人」,更是明確的教導我們要不斷提高自身的內聚性,最大限度地減少給他人造成的麻煩,從而達到安人、安百姓、安天下的目標。我想,成長的過程就是一個不斷提升內聚的過程。「自己的東西自己保管,自己的事情自己做」,這些孩提時代的教誨,放到今天仍能讓不少「大人」臉紅不已。太多的人保管不好自己的「東西」,保管不好自己的身體,保管不好自己的婚姻,更保管不好自己如蛛絲般震顫飄蕩的狂亂的心。至於做好自己的事情,則更是惘然,甚至很多人連自己的事情是什麼都搞不清楚,因此渾渾噩噩,飽食終日。內聚,是一個值得我們好好反思的問題。
·依賴·耦合在面向對象編程中,對象自身是內聚的,是保管好自己的數據,完成好自己的操作的,而對外界呈現出自己的狀態和行為。但是,沒有絕對的自力更生,對外開放也是必要的!一個對象,往往需要跟其他對象打交道,既包括獲知其他對象的狀態,也包括仰賴其他對象的行為,而一旦這樣的事情發生時,我們便稱該對象依賴於另一對象。只要兩個對象之間存在一方依賴一方的關係,那麼我們就稱這兩個對象之間存在耦合。 比如媽媽和baby,媽媽要隨時關注baby的睡、醒、困、哭、尿等等狀態,baby則要仰賴媽媽的餵奶、哄睡、換紙尿褲等行為,從程序的意義上說,二者互相依賴,因此也存在耦合。首先要說,耦合是必要的。我們來看以下這個實驗。
【王陽明與山中之花】
View Code由於王陽明這個對象不依賴山花這個對象,又沒有其他的方式來獲知山花的盛開狀態,所以他要麼選擇不說,要麼瞎說,但不說編譯是通不過,而瞎說作為王陽明來講也是通不過的,所以這個系統是無法成立的。要想系統成立,必須要這樣寫:
public bool AdmireFlowers()
{
return flower.IsBloomed; ;
}
無論這個山花對象是怎麼來的,作為參數傳入還是作為屬性設置、還是在內部構造出來,總之,王陽明與山花之間發生了依賴,二者之間產生了耦合。 當然,這是一個很淺顯的問題。有趣的是王陽明對此事的看法:「你未看花時,花與你同寂;你來看花,花於你則一時分明起來。可見心外無物!」王陽明講的是對的!「心外無物」翻譯技術語言是這樣的:不存在耦合的兩個對象必然拿不到對方的引用!
·耦合度·解耦和耦合的程度就是耦合度,也就是雙方依賴的程度。上文所說的媽媽和baby就是強耦合。而你跟快遞小哥之間則是弱耦合。一般來說耦合度過高並不是一件好事。就拿作為IT精英的你來說吧,上級隨時敦促你的工作進度,新手頻繁地需要你指導問題,隔三差五還需要參加酒局飯局,然後還要天天看領導的臉色、關注老婆的心情,然後你還要關注代碼中的bug 、bug、bug,和需求的變化、變化、變化,都夠焦頭爛額了,還猝不及防的要關注眼睛、頸椎、前列腺和頭髮的狀態,然後你再炒個股,這些加起來大概就是個強耦合了。從某種意義上來說,耦合天生就與自由為敵,無論是其他對象依賴於你,還是你依賴其他對象。比如有人嗜煙、酗酒,你有多依賴它們就有多不自由;比如有人家裡生了七八個娃,還有年邁的父母、岳父母,他們有多依賴你,你就有多不自由。所以老子這樣講:「五音令人耳聾,五色令人目盲,馳騁狩獵令人心發狂,難得之貨令人行妨。」盧梭也是不無悲涼的說「人生而自由,卻又無往而不在枷鎖中」。因此,要想自由,就必須要降低耦合,而這個過程就叫做解耦和。
·依賴倒置(Dependence Inversion Principle)解耦和最重要的原則就是依賴倒置原則:
高層模塊不應該依賴底層模塊,他們都應該依賴抽象。抽象不應該依賴於細節,細節應該依賴於抽象。
《資本論》中都曾闡釋依賴倒轉原則——在商品經濟的萌芽時期,出現了物物交換。假設你要買一個IPhone,賣IPhone的老闆讓你拿一頭豬跟他換,可是你並沒有養豬,你只會編程。所以你找到一位養豬戶,說給他做一個養豬的APP來換他一頭豬,他說換豬可以,但是得用一條金項鏈來換——所以這裡就出現了一連串的對象依賴,從而造成了嚴重的耦合災難。解決這個問題的最好的辦法就是,買賣雙發都依賴於抽象——也就是貨幣——來進行交換,這樣一來耦合度就大為降低了。
再舉一個編程中的依賴倒置的例子。我們知道,在通信中,消息的收發和消息的處理往往密不可分。就一般的通信框架而言,消息的收發通常是已經實現了的,而消息的處理則是需要用戶來自定義完成的。先看一個正向依賴的例子:輕量級通信引擎StriveEngine。tcpServerEngine是StriveEngine.dll提供通信引擎,它發布有一個MessageReceived事件。假設我定義了一個CustomizeHandler類來用於消息處理,那麼CustomizeHandler的內部需要預定tcpServerEngine的MessageReceived事件,因此customizeHandler依賴於tcpServerEngine,這就是一個普通的依賴關係,也就是高層模塊依賴於低層模塊。
而ESFramework通信框架則應用了依賴倒轉原則。ESFramework定義了一個IcustomizeHandler介面,用戶在進行消息處理時,實現該介面,然後將其注入到rapidPassiveEngine客戶端通信引擎之中。
View Code
很明顯,相比於上一個例子,這裡的依賴關係變成了rapidPassiveEngine依賴於customizeHandler,也就是說依賴關係倒置了過來,上層模塊不再依賴於底層模塊,而是它們共同依賴於抽象。rapidPassiveEngine依賴的是IcustomizeHandler介面類型的參數,customizeHandler同樣是以實現的介面的方式依賴於IcustomizeHandler——這就是一個依賴倒置的典範。
·控制反轉(Inversion of Control)控制反轉跟依賴倒置是如出一轍的兩個概念,當存在依賴倒置的時候往往也存在著控制反轉。但是控制反轉也有自己的獨特內涵。
首先我們要區分兩個角色,server 跟 Client,也就是服務方和客戶方。提供服務端的一方稱為服務方,請求服務的一方稱為客戶方。我們最熟悉的例子就是分散式應用的C/S架構,服務端和客戶端。其實除此之外,C/S關係處處可見。比如在TCP/IP協議棧中,我們知道,每層協議為上一層提供服務,那麼這裡就是一個C/S關係。當我們使用開發框架時,開發框架就是作為服務方,而我們自己編寫的業務應用就是客戶方。當Client調用server時,這個叫做一般的控制;而當server調用Client時,就是我們所說的控制反轉,同時我們也將這個調用稱為「回調」。控制反轉跟依賴倒置都是一種編程思想,依賴倒置著眼於調用的形式,而控制反轉則著眼於程序流程的控制權。一般來說,程序的控制權屬於server,而一旦控制權交到Client,就叫控制反轉。比如你去下館子,你是Client餐館是server。你點菜,餐館負責做菜,程序流程的控制權屬於server;而如果你去自助餐廳,程序流程的控制權就轉到Client了,也就是控制反轉。
控制反轉的思想體現在諸多領域。比如事件的發布/ 訂閱就是一種控制反轉,GOF設計模式中也多處體現了控制反轉,比如典型的模板方法模式等。而開發框架則是控制反轉思想應用的集中體現。比如之前所舉的ESFramework通信框架的例子,通信引擎回調用戶自定義的消息處理器,這就是一個控制反轉。以及ESFramework回調用戶自定義的群組關係和好友關係,回調用戶自定義的用戶管理器以管理在線用戶相關狀態,回調用戶自定義的登陸驗證處理,等等不一而足。再比如與ESFramework一脈相承的輕量級通信引擎StriveEngine,通過回調用戶自定義的通信協議來實現更加靈活的通信。
由此我們也可以總結出開發框架與類庫的區別:使用開發框架時,框架掌握程序流程的控制權,而使用類庫時,則是應用程序掌握程序流程的控制權。或者說,使用框架時,程序的主循環位於框架中,而使用類庫時,程序的主循環位於應用程序之中。框架會回調應用程序,而類庫則不會回調應用程序。ESFramework和StriveEngine中最主要的對象都以engine來命名,我們也可以看出框架對於程序主循環的控制——它會為你把握方向、眼看前方、輕鬆駕馭!
·依賴注入(Dependency Injection)依賴注入與依賴倒置、控制反轉的關係仍舊是一本萬殊。依賴注入,就其廣義而言,即是通過「注入」的方式,來獲得依賴。我們知道,A對象依賴於B對象,等價於A對象內部存在對B對象的「調用」,而前提是A對象內部拿到了B對象的引用。B對象的引用的來源無非有以下幾種:A對象內部創建(無論是作為欄位還是作為臨時變數)、構造器注入、屬性注入、方法注入。後面三種方式統稱為「依賴注入」,而第一種方式我也生造了一個名詞,稱為「依賴內生」,二者根本的差異即在於,我所依賴的對象的創建工作是否由我自己來完成。當然,這個是廣義的依賴注入的概念,而我們一般不會這樣來使用。我們通常使用的,是依賴注入的狹義的概念。不過,直接陳述其定義可能會過於詰屈聱牙,我們還是從具體的例子來看。
比如OMCS網路語音視頻框架,它實現了多媒體設備(麥克風、攝像頭、桌面、電子白板)的採集、編碼、網路傳送、解碼、播放(或顯示)等相關的一整套流程,可以快速地開發出視頻聊天系統、視頻會議系統、遠程醫療系統、遠程教育系統、網路監控系統等等基於網路多媒體的應用系統。然而,OMCS直接支持的是通用的語音視頻設備,而在某些系統中,需要使用網路攝像頭或者特殊的視頻採集卡作為視頻源,或者其它的聲音採集設備作為音頻源,OMCS則提供了擴展介面——用戶自己實現這個擴展的介面,然後以「依賴注入」的方式將對象實例注入到OMCS中,從而完成對音、視頻設備的擴展。
「依賴注入」常常用於擴展,尤其是在開發框架的設計中。從某種意義上來說,任何開發框架,天生都是不完整的應用程序。因此,一個優秀的開發框架,不僅要讓開發者能夠重用這些久經考驗的的卓越的解決方案,也要讓開發者能夠向框架中插入自定義的業務邏輯,從而靈活自由地適應特定的業務場景的需要——也就是說要具備良好的可擴展性。比如上面提到的OMCS網路語音視頻框架可應用於音、視頻聊天系統、視頻會議系統、遠程醫療系統、遠程教育系統、網路監控系統等等基於網路多媒體的應用系統;以及ESFramework通信框架能夠應用於即時通訊系統,大型多人在線遊戲、在線網頁遊戲、文件傳送系統、數據採集系統、分散式OA系統等任何需要分散式通信的軟體系統中——這種良好的擴展性都與「依賴注入」的使用密不可分!
·面向介面編程談到最後,「面向介面編程」已經是呼之欲出。無論是依賴倒置、控制反轉、還是依賴注入,都已經蘊含著「面向介面編程」的思想。面向介面,就意味著面向抽象。作為哲學範疇而言,規定性少稱為抽象,規定性多稱為具體。而介面,就是程序中的一種典型的「抽象」的形式。面向抽象,就意味著面向事物的本質規定性,擺脫感性雜多的牽絆,從而把握住「必然」——而這本身就意味著自由,因為自由就是對必然的認識。
也許以上的這段論述太過「哲學」,但是「一本之理」與「萬殊之理」本身就「體用不二」——總結來看,依賴倒置、控制反轉、依賴注入都圍繞著「解耦和」的問題,而同時自始至終又都是「面向介面編程」的方法——因此,「面向介面編程」天生就是「解耦和」的好辦法。由此也印證了從「抽象」到「自由」的這一段範疇的辯證衍化。
「面向對象」與「面向介面」並非兩種不同的方法學,「面向介面」其實是「面向對象」的內在要求,是其一部分內涵的集中表述。我們對於理想軟體的期待常被概括為「高內聚,低耦合」,這也是整個現代軟體開發方法學所追求的目標。面向對象方法學作為現代軟體開發方法學的代表,本身就蘊含著「高內聚,低耦合」的思想精髓,從這個意義上來說,「面向對象」這個表述更加側重於「高內聚」,「面向介面」的表述則更加側重於「低耦合」——不過是同一事物的不同側面罷了。
除此之外,我們也能從「面向介面編程」的思想中得到「世俗」的啟迪——《論語》裡面講,不患無位,患所以立;不患人之不己知,患其不能也——就是教導我們要面向「我有沒有的本事?」、「我有沒有能力?」這樣的介面,而不是面向「我有沒有搞到位子?」、「別人了不了解我?」這樣的具體。依我看,這是莫大的教誨!
我認為面向對象不能稱之為「技術」
它是一種思想
面向對象將數據和操作集合在一起,要說它反模塊化也有道理。面向對象代碼比較傾向於「有狀態」,要說它反並行也有道理。就學了一陣子haskell的體驗來說,現在大部分編碼工人怕是很難掌握函數式編程的,不過覺得對將來的軟體工業是好事。
他們為什麼說面向對象有問題,探討面向對象的一些缺陷
最
近跟某位朋友討論了一些工作上的事情,他目前就職於某世界500強IT公司,在他們現在做的一個項目中,整個系統構架是完全面向對象的,而且他對這種框架
極其推崇,不過他們經常加班到深夜,有時周末也要加班,筆者當時從直覺上覺得這裡有問題,回去之後仔細反思,搜索了一些資料,算是找到了他們為什麼這麼累
的原因吧。
面向對象(Object Oriented,OO)是當前計算機界關心的重點,它是90年代軟體開發方法的主流,不過隨著時代的發展,很多人對OO編程方法的看法也出現了一些變化;
最近在網上面看到有個名人噴面向對象的文章,我們可以先看一看:
「面向對象編程是一個極其糟糕的主意,只有矽谷里的人能幹出這種事情。」 — Edsger Dijkstra(圖靈獎獲得者)
「面向對象設計是用羅馬數字做計算。」 — Rob Pike(Go語言之父)
「「面向對象」這個詞包含很多意思。有一半是顯而易見的,而另一半是錯誤的。「 — Paul Graham(美國互聯網界如日中天的教父級人物)
「實現上的繼承就跟過度使用goto語句一樣,使程序擰巴和脆弱。結果就是,面向對象系統通常遭受複雜和缺乏復用的痛苦。」 — John Ousterhout( Tcl and Tk 的創始人) Scripting, IEEE Computer, March 1998
「90%的這些胡說八道都稱現在它很流行,非要往我的代碼里搓揉進面向對象的石粒。」 — kfx
「有時,優雅的實現只需要一個函數。不是一個方法。不是一個類,不是一個框架。只是一個方法。」 — John Carmack(id Software的創始人、第一人稱射擊遊戲之父)
「面向對象編程語言的問題在於,它總是附帶著所有它需要的隱含環境。你想要一個香蕉,但得到的卻是一個大猩猩拿著香蕉,而其還有整個叢林。」 — Joe Armstrong(Erlang語言發明人)
「我一度曾經迷戀上了面向對象編程。現在我發現自己更傾向於認為面向對象是一個陰謀,企圖毀掉我們的編程樂趣。」 — Eric Allman(sendmail的創造者)
——摘自: 面向對象編程從骨子裡就有問題
下面我們來分析一下他們為什麼討厭面向對象,過度追求OO到底有什麼問題:
面向對象的生產效率
這是OO編程比較推崇的一點,不過我們可以先看看這篇文章: 如此理解面向對象編程
在文章中,某「黑客」,將下面一段代碼,「面向對象」成了7個文件,代碼量由原來的幾行變成了幾十行。
public class PrintOS
{
public static void main(final String[] args)
{
String osName = System.getProperty("os.name") ;
if (osName.equals("SunOS") || osName.equals("Linux"))
{
System.out.println("This is a UNIX box and therefore good.") ;
}
else if (osName.equals("Windows NT") || osName.equals("Windows 95"))
{
System.out.println("This is a Windows box and therefore bad.") ;
}
else
{
System.out.println("This is not a box.") ;
}
}
}
文中的這位「黑客」將學院派的做法發揮到了極致。雖然這只是一個OO的教學文檔,但確實很像OO的一篇高級黑,但我們仍能看出一味地追求OO會增加多麼大的代碼量。你現在明白你為什麼需要IDE來輔助了吧? 在茫茫多的文件中尋找一個變數是多麼的不容易呀。
面向對象的性能
這篇文章詳細地討論了OO的性能: Pitfalls Of Object Oriented Programming.
文中指出,從1980年至今,CPU的性能提高了近10W倍,但內存的訪問性能只提高了不足10倍,原因就是內存的訪問相對速度大為降低,OO的濫用使程序要經過很多間接過程才能訪問到目標內存地址,
因而過度使用面向對象,會嚴重影響性能, 作者推薦優化數據和代碼結構為先,並盡量使用KISS(keep it simple, stupid)原則來設計軟體。
面向對象的可維護性
面向對象從一開始就要求我們完全了解各個子類的不同,並將他們的「共性」提取到父類里,從而實現代碼復用,
在這個過程中自然形成了一種最強的耦合關係。這種設計方法在需求非常確定的情況下是有效的,但在實際生活中我們發現需求總是在開發過程中不斷提出的,而且
也總在變化,甚至跟之前完全相反,當你看到你精心設計的框架成為你需求變更的障礙時,你做何感想?
在一個完全OO的系統中,我們會不自覺地使用設計模式驅動型編程,正如這種編程的名字所說的,這種編程風格使用大量的設計模式,在你的程序中,四處
都是設計模式,你的代碼到處都是Facade,Observer
,Strategy,Adapter,等等等等。於是,你的程序要處理的業務邏輯被這些設計模式打亂得無法閱讀,最後,也不知道是業務需求重來,還是設計
模式重要,總之,實際業務需求的程序邏輯被各種設計模式混亂得不堪入目。摘自:各種流行的編程風格
該如何面向對象
本文並不是完全否定面向對象的編程方法,經過上面的分析,我們已經可以發現,OO比較適合需求確定,耦合度高的子模塊中使用; 但在一個需求在不斷變化,業務邏輯在不斷增加的複雜系統中,面向模塊也許是更好的選擇。
http://ourjs.com/detail/528b2d6ab5cbfd990b000002
(豁然開朗)《面向對象分析與設計》讀書筆記 (1)- 關鍵的思想 - 知乎專欄
這裡面進行了較為全面的介紹。
OOP的優點是可以提高程序員對複雜性的管理能力,前提是正確的設計和運用。
缺點是容易讓程序演算法變得晦澀,最近看unix發明人湯普森在訪談中就反感現代程序設計,包括分層和所謂的代理之類的。目前看OOP,AOP,FP的多范型編程是目前的主流。對程序員來說,還是演算法是最核心的東西。面向過程是make things,面向對象是make things happen。
不得不說,在處理大的工程時,OOP是行之有效的解決手段~
推薦閱讀:
※如何從零基礎做出類似拳皇一類的動作類2D遊戲?
※SPFA演算法可否取代Dijkstra演算法成為計算單源最短路徑的最優解?
※彙編指令集與cpu指令集是什麼關係?
※Hash時取模一定要模質數嗎?
※易語言有哪些優點?