20 世紀 60 年代,程式設計遇到了一個大問題:計算機還沒有那麼強大,需要以某種方式平衡資料結構和程式之間的能力。
這意味著,如果你有大量資料,那麼不將計算機推向極限就無法充分利用這些資料。另外,如果你需要做很多事情,那麼你就不能使用過多的資料,否則計算機將會一直執行下去。
接下來到了 1966、1967 年,Alan Kay 從理論上證明可以使用封裝的微型計算機。這些微型計算機不共用資料,而是通過訊息傳遞進行通訊。這樣就可以更加經濟地使用計算資源。
儘管這個想法很巧妙,但直到 1981 年,物件導向程式設計才成為主流。在那之後,它就沒有停止過吸引新的和經驗豐富的軟體開發者。物件導向的程式設計師市場一如既往地忙碌。
但是在最近幾年中,這種已有幾十年歷史的程式設計正規化受到越來越多的批評。難道是在物件導向程式設計大行其道 40 年之後,技術已經超越了這種正規化?
物件導向程式設計的主要思想非常簡單:嘗試將一個功能強大的程式整體分解為功能同樣強大的多個部分。這樣就可以將一些資料和那些只在相關資料上使用的函數耦合起來。
注意,這僅涵蓋封裝的概念。也就是說,位於物件內部的資料和函數對於外部是不可見的。我們只能通過訊息(通常通過 getter 和 setter 函數)與物件的內容進行互動。
繼承性和多型性並沒有包含在最初的設計想法中,但是對於現在的物件導向程式設計而言是必需的。繼承基本上意味著開發者可以定義具有其父類別所有屬性的子類。直到 1976 年,即物件導向的程式設計的概念問世十年之後,繼承性才被引入。
又過了十年,多型性才進入物件導向的程式設計。簡單來講,這意味著某種方法或物件可以用做其他方法或物件的模板。從某種意義上說,多型性是繼承性的泛化,因為並不是原始方法或物件的所有屬性都需要傳輸到新實體。相反,你還可以選擇重寫一些屬性。
多型性的特殊之處在於,即使兩個實體在原始碼中互相依賴,被呼叫實體的工作方式也更像外掛。這使得開發人員的工作變得輕鬆,因為他們不必擔心執行時的依賴關係。
值得一提的是,繼承性和多型性並不是物件導向程式設計所特有的。真正的區別在於封裝資料及其包含的方法。在計算資源比今天稀缺得多的時代,這是一個天才的想法。
物件導向的程式設計一經問世,便改變了開發人員看待程式碼的方式。20 世紀 80 年代以前,程式式程式設計非常面向機器。開發人員需要非常瞭解計算機的工作原理才能編寫好的程式碼。
通過封裝資料和其他方法,物件導向的程式設計使軟體開發更加以人為中心,符合人類的直覺。比如,方法 drive() 屬於 car 資料組,而不是 teddybear 組。之後出現的繼承性也很直觀。比如,現代汽車(Hyundai)是汽車的一個子類,並且具有相同的屬性,但 PooTheBear 不是,這樣很好理解。
香蕉猴子叢林問題
想象一下,你正在設定一個新程式,並且正在考慮設計一個新類。然後,你回想起為另一個專案建立的簡潔的小類,發現其對正在進行的工作很合適。
沒問題,你可以將以前專案中的類在新專案中複用。
這裡有一個問題:這個類可能是另一個類的子類,因此你需要將它的父類別也包含在內。然後你會發現,這個父類別可能也是另一個類的子類,以此類推,最後要面對一堆程式碼。
Erlang 的建立者 Joe Armstrong 曾有一句名言:「物件導向語言的問題在於,它們自帶其自身周圍的所有隱式環境。你想要香蕉,但是得到的卻是拿著香蕉的大猩猩和整個叢林。」
這幾乎可以說明一切。複用類是可以的,實際上這可能是物件導向程式設計的主要優點,但不要將其發揮到極致。有時你應該建立一個新的類,而不是新增大量依賴項。
脆弱的基礎類別問題
想象一下,如果你已經成功地將另一個專案中的類複用於新的程式碼,那麼如果基礎類別發生變化會怎樣?
這可能會破壞你整個新專案的程式碼,即使你可能什麼也沒做。一旦有人更改了基礎類別中的一個細節,而這一點又對你的專案至關重要,那麼這種影響將是非常大並且突然的。
使用繼承的次數越多,潛在的維護工作就越多。因此,即使在短期內複用程式碼非常有效,但從長遠來看,它可能讓你付出一定的代價。
菱形繼承問題
利用繼承可以將一類中的屬性傳遞給其他類。但是,如果你想混合兩個不同類的屬性怎麼辦?
沒錯,這無法完成,至少常規的方法都不行。以 Copier 類為例(在此參照以下連結文章中的例子:https://medium.com/@cscalfani/goodbye-object-oriented-programming-a59cda4c0e53),Copier 將掃描檔案的內容並將其列印在白紙上。那麼它應該是 Scanner 還是 Printer 的子類?
這個問題根本沒有完美的答案。即使這個問題不會破壞你的程式碼,但它經常出現,會讓人很沮喪。
層級問題
在菱形繼承問題中,Copier 是哪個類的子類是問題的關鍵所在。但或許有個投機取巧的方案:假設 Copier 是父類別,Scanner 和 Printer 是僅繼承屬性子集的子類,那麼問題就解決了。
但如果你的 Copier 是黑白的,而 Printer 也能夠處理彩色,那怎麼辦?從這個意義上說,Printer 不是 Copier 的一種泛化嗎?如果 Printer 連線了 WiFi,而 Copier 沒有呢?
類上堆積的屬性越多,建立適當的層次結構就越困難。在你所處理的屬性叢集中,Copier 共用了 Printer 的一些屬性,但不是全部屬性,反之亦然。在大型複雜專案中,層次結構的問題會導致很大的混亂。
參照問題
你可能會想到進行沒有層次結構的物件導向程式設計。我們可以使用屬性叢集,並根據需要繼承、擴充套件或重寫屬性。也許這有點混亂,但這將是對當前問題的準確表示。
這裡只存在一個問題:封裝的全部目的是使資料片段彼此之間保持安全,從而使計算效率更高,但沒有嚴格的層次結構,這是行不通的。
假設一個物件 A 通過與另一個物件 B 互動來覆蓋層次結構,會發生什麼情況?其他關係的情況並不重要,但當 B 不是 A 的直接父類別時,A 必須包含 B 的全部私有參照,否則,它們將無法互動。
但是,如果 A 包含 B 的子類也具有的資訊,那麼就可以在多個位置修改該資訊。因此,有關 B 的資訊已經不再安全,並且封裝已經被破壞。
儘管許多物件導向的程式設計師都使用這種架構來構建程式,但這並不是物件導向程式設計,只是一團糟。
單一正規化存在的風險
以上 5 個問題的共同點是它們都存在不合適的繼承。由於繼承沒有包含在物件導向程式設計的原始形式中,所以這些問題可能不能稱為物件導向本身的問題。
但是也並不是只有物件導向程式設計會被誇大。在純粹的函數語言程式設計中,處理使用者的輸入或在螢幕上輸出訊息極其困難。對此,物件導向或程式導向程式設計會好很多。
但仍然有一些開發人員試圖將這些東西用純函數的方式實現,並且編寫幾十行沒人能看懂的程式碼。而使用另一種正規化就能夠輕鬆地將程式碼簡化為幾行可讀的程式碼。
毫無疑問,函數語言程式設計正在得到更多關注,而物件導向程式設計近幾年遭到一些詬病。瞭解新的程式設計正規化並在適當的時候使用它們是很有意義的。無論哪種程式設計正規化,都不需要只遵循一種,在適當的時候使用不同的程式設計正規化才能更好地解決問題。
面對越來越多的問題,函數語言程式設計可能是更有效的一種選擇。資料分析、機器學習、並行程式設計,這些領域你投入的越多,你就會越喜歡函數語言程式設計。
但是目前物件導向開發的程式設計師的崗位需求量依然比函數語言程式設計開發程式設計師多得多。但是這也並不意味著你不能成為後者,函數語言程式設計開發的程式設計師目前仍然比較稀缺。
最有可能的情況是,物件導向的程式設計將會繼續存在十年左右。當然,選擇相對前衛的方式是好的,但這並不意味著你應該放棄物件導向程式設計。所以在接下來的幾年中,不要完全放棄它,但至少確保它不是你唯一掌握的程式設計方式。
推薦閱讀:
為什麼阿里巴巴的程式設計師成長速度這麼快,看完他們的內部資料我頓悟了
原來優秀程式設計師簡歷都是這麼寫的,你學會你也可以進大廠!【附阿里p6簡歷分享】
阿里二面遭調優爆錘,閉關13天啃透這本效能實戰手冊,35K成功入職美團
作者親談《阿里巴巴Java開發手冊》背後的故事;附阿里內部資料分享。
如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:
點贊,轉發,有你們的 『點贊和評論』,才是我創造的動力。
關注公眾號 『 Java鬥帝 』,不定期分享原創知識。
同時可以期待後續文章ing🚀