- 原文地址:The Forgotten History of OOP
- 原文做者:Eric Elliott
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:geniusq1981
注: 這是 "組合軟件" 系列的一部分,旨在從頭開始學習 Javascript ES6+ 中的函數式編程和組合軟件技術。更多內容即將推出,敬請關注! < 上一篇 | << Start over at Part 1javascript
咱們今天使用的函數式和命令式編程範例最先出現於 20 世紀 30 年代,當時使用 Lambda 演算和圖靈機進行數學探索,它們是通用計算(可執行通用計算的形式化系統)的替代公式。Church Turing 理論代表,lambda 演算和圖靈機是等價的,任何使用圖靈機能夠作的計算均可以使用 lambda 演算來計算,反之亦然。前端
注:有一個廣泛的誤解,認爲圖靈機能夠計算任何可計算的問題。可是有一些問題(例如,停機問題)在某些狀況下是可計算的,但有些狀況倒是使用圖靈機沒法計算的。當我在本文中使用「可計算」一詞時,個人意思是「能夠由圖靈機計算」。java
Lambda 演算表明一種自上而下的函數式應用計算方法,而圖靈機的紙帶/寄存器式的機器公式則表明一種自下而上的、命令式(步進式)計算方法。android
從 20 世紀 40 年代,開始出現像機器碼和彙編語言這樣的低級語言,直到 20 世紀 50 年代末,出現了第一個流行的高級語言。Lisp 語言,固然還包括 Clojure,Scheme,AutoLISP 等語言,直到今天還在被普遍使用。FORTRAN 和 COBOL 都出如今 20 世紀 50 年代,但直到今天還在被看成命令式高級語言的範例在使用,儘管對於大多數應用程序來講,C 族語言已經取代了 COBOL 和 FORTRAN。ios
命令式編程和函數式編程都起源於計算數學理論,它們比數字計算機還要早。「面向對象編程」(OOP)是艾倫·凱(Alan Kay)在 1966 年或 1967 年讀研究生時創造的。git
Ivan Sutherland 創新的 Sketchpad 應用程序是 OOP 的早期靈感。它開發於 1961 年至 1962 年,並於 1963 年發表在他的 Sketchpad 論文 中。其中對象是表示在示波器屏幕上顯示的圖像的數據結構,而且經過動態表示具備繼承特性,Ivan Sutherland 在他的論文中稱之爲「masters」。任何對象均可以成爲「master」,而對象的其餘實例稱爲「occurrences」。Sketchpad 的 masters 和 JavaScript 的原型鏈繼承有不少共同之處。程序員
注意: 麻省理工學院林肯實驗室的 TX-2 是使用激光筆直接進行屏幕交互的圖形計算機顯示器的早期用途之一。1948-1958 運行的 EDSAC,能夠在屏幕上顯示圖形。1949 年,麻省理工學院的「旋風」項目使用了一個示波器。該項目的動機是建立一個可以模擬多架飛機儀器反饋的通用飛行模擬器。這引領了 SAGE 計算系統的發展。TX-2 是 SAGE 的測試計算機。github
提出於 1965 年的 simula 語言,是第一個被普遍承認的「面向對象」的編程語言。與 Sketchpad 同樣,Simula 使用了對象,甚至還引入了類,類繼承,子類和虛方法。編程
注意:虛方法是定義在類上的方法,它被設計爲能夠被子類重寫。虛方法容許程序動態指定在編譯代碼時可能不存在的方法,以此肯定在運行時調用哪一個具體方法。JavaScript 具備動態類型,並使用委託鏈來肯定要調用的方法,所以不須要向開發者再提供虛方法的概念。換句話說,JavaScript 中的全部方法都是運行時進行動態調度,所以 JavaScript 中的方法不須要聲明爲「虛方法」。後端
「我使用‘面向對象’這個術語,我告訴你我並無考慮到 C++。」 Alan Kay,OOPSLA ‘97
Alan Kay 在 1966 或 1967 年間在研究生院創造了「面向對象編程」一詞。這個偉大的想法是想在軟件中使用封裝好的微型計算機,經過消息傳遞而不是直接的數據共享,以此來防止將程序分裂爲單獨的「數據結構「和「程序」。
「遞歸設計的基本原理是使局部具備與總體相同的能力。」BB Barton,B5000 的主要設計者,這是一個通過優化來運行 Algol-60 的主機。
Smalltalk 由 Alan Kay、Dan Ingalls、Adele Goldberg 和 Xerox PARC 的其餘人一塊兒開發。Smalltalk 比 Simula 更加面向對象 — Smalltalk 中的全部東西都是對象,包括類,整數和塊(閉包)。最初的 Smalltalk-72 不支持建立子類。建立子類是由 Dan Ingalls 在 Smalltalk-76 中引入的。
儘管 Smalltalk 支持類並最終支持建立子類,但 Smalltalk 不僅是關於類或建立子類的。它是受到 Lisp 和 Simula 啓發的功能語言。Alan Kay 認爲業界對子類過分關注而由此忽略了面向對象編程帶來的真正好處。
「我很抱歉,我好久之前爲這個話題創造了「對象」一詞,而由於它讓不少人專一於細枝末節。實際上真正重要的是消息傳遞。」 ~ Alan Kay
在 2003 年的電子郵件 中,Alan Kay 闡明瞭他爲何稱 Smalltalk 爲「面向對象」:
「OOP 對我來講意味着消息傳遞,對狀態進程的本地保留保護和隱藏,以及對全部事物的動態綁定。」 ~ Alan Kay
換句話說,根據 Alan Kay 的說法,OOP 的基本要素是:
顯然,創造了這個術語,併爲咱們帶來 OOP 的 Alan Kay 並不認爲繼承和子類多態性是 OOP 的必要組成部分。
消息傳遞和封裝的結合有一些重要的目的:
這些想法的靈感來自於生物細胞和/或網絡上的我的計算機,受到 Alan Kay 的生物學背景和 Arpanet(早期版本的互聯網)的設計影響。在想法提出以前,Alan Kay 就想象軟件運行在巨大的分佈式計算機(互聯網)上,我的計算機就像生物細胞同樣,在本身的孤立狀態下獨立運行,並經過消息傳遞進行通訊。
「我意識到細胞與整個計算機類比會脫離數據 [...]」 ~ Alan Kay
經過「脫離數據」,Alan Kay 意識到共享可變狀態的問題和由共享數據引發的耦合問題 — 這些都是今天的廣泛問題。
可是在 20 世紀 60 年代後期,ARPA 程序員對在程序編寫以前選擇數據模型的需求感到沮喪。程序與特定數據結構過分耦合使得程序沒法適應變化。他們但願對數據進行更加統一的處理。
「[...] OOP 的核心是不須要再關心對象內部的內容。使用不一樣機器和不一樣語言開發的對象應該能夠互相交通訊 [...]」 ~ Alan Kay
對象能夠被抽象出來並隱藏數據結構的實現方法。對象的內部實現能夠在不破壞軟件系統其餘部分的狀況下進行更改。實際上,經過遲約束,徹底不一樣的計算機系統能夠接管對象的功能,而且軟件能夠繼續工做。與此同時,對象能夠公開一個標準接口,該接口能夠處理對象在內部使用的任何數據結構。相同的接口可使用鏈表、樹和流等。
Alan Kay 還將對象視爲代數結構,這些結構能夠經過數學來證實它們的行爲:
「個人數學背景使我意識到每一個對象可能有幾個與之相關的代數式,可能有代數式族,而這些將會很是有用。」 ~ Alan Kay
這已經被證實是對的,而且構成了 promises 和 lenses 等對象的基礎,這兩個對象都受到類別理論的啓發。
Alan Kay 對於對象具備代數性質的觀點將容許對象提供公式化驗證,肯定性行爲和改進的可測試性,由於代數本質上是遵循方程式規則的操做。
在程序員術語中,代數就像是由函數(操做)組成的抽象,這些函數必須經過單元測試強制執行的特定規則(公理/方程式)。
這樣的想法在大多數 C 族面嚮對象語言中被遺忘了幾十年,包括 C++、Java 和 C# 等,但如今在它們的最新版本中正在逐漸找回這樣的特性。
你可能會說編程世界正在從新發現面嚮對象語言環境中的函數式編程和理性思惟帶來的優點。
就像以前的 JavaScript 和 Smalltalk 同樣,大多數的現代面嚮對象語言正變得愈來愈「多範式」。已經沒有理由在函數式編程和麪向對象編程之間進行選擇。當咱們追溯它們各自的歷史本源時,它們是既相互兼容,又相互互補的。
由於它們有如此多共通的特性,我想說 JavaScript 是針對世界對面向對象編程的誤解的一種反擊。Smalltalk 和 JavaScript 都支持:
什麼是面向對象編程的必要特性(根據 Alan Kay 的說法)?
什麼是非必要的特性?
new
關鍵字若是您是 Java 或 C# 的開發者,您可能會認爲靜態類型和多態性是必不可少的成分,但Alan Kay 傾向於以代數形式處理共性行爲。例如,來自 Haskell:
fmap :: (a -> b) -> f a -> f b
複製代碼
這是仿函數映射簽名,它一般用於未指定類型 a
和 b
,在 a
的仿函數的上下文中應用從 a
到 b
的函數來生成 b 的仿函數
。仿函數是數學術語,含義是「支持映射操做」。若是您熟悉 JavaScript 中的 [].map()
,那麼您已經知道它的含義是什麼。
這是 JavaScript 中的兩個例子:
// isEven = Number => Boolean
const isEven = n => n % 2 === 0;
const nums = [1, 2, 3, 4, 5, 6];
// map takes a function `a => b` and an array of `a`s (via `this`)
// and returns an array of `b`s.
// in this case, `a` is `Number` and `b` is `Boolean`
const results = nums.map(isEven);
console.log(results);
// [false, true, false, true, false, true]
複製代碼
.map()
方法是通用的,由於 a
和 b
能夠是任何類型,而 .map()
處理它剛恰好,由於數組是代數上實現 functor
規則的數據結構。類型與 .map()
無關,由於它不會嘗試直接操做它們,而是經過一個函數來返回正確的指望類型。
// matches = a => Boolean
// here, `a` can be any comparable type
const matches = control => input => input === control;
const strings = ['foo', 'bar', 'baz'];
const results = strings.map(matches('bar'));
console.log(results);
// [false, true, false]
複製代碼
泛型類型關係很難在 TypeScript 這樣的語言中徹底正確地表達,但在 Haskell 的 Hindley Milner 類型中很容易表達,由於它支持更高級的類型(類型的類型)。
大多數類型系統有太多的限制,不容許自由表達動態和函數的想法,例如函數組合,自由對象組合,運行時對象擴展,組合器,鏡頭等。換句話說,靜態類型常常會使得組合軟件的編寫更加困難。
若是類型系統限制太多(例如 TypeScript,Java),您將不得不爲了實現相同的目標編寫更加複雜的代碼。這並非說靜態類型是一個糟糕的主意,或者說全部靜態類型的實現都具備一樣的限制性。我使用 Haskell 類型系統遇到的問題就不多。
若是你熱衷靜態類型,你不會介意這些限制,這會是你更加有掌控力,可是若是你發現一些本文中建議中提到的那些困難,好比很難輸入組合函數和複合代數結構,所以就歸咎於類型系統,這樣並非可取的想法。人們喜歡 SUV 能給你帶來溫馨性,但不會有人抱怨 SUV 不能讓你飛起來。若是爲了追求那樣的體驗,您須要一輛擁有更多自由度的車輛。
若是這樣的限制能使您的代碼更簡單,那就太棒了!可是若是這些限制迫使你編寫更復雜的代碼,那麼這樣的限制可能就是錯誤的。
多年來,對象已經被賦予大量的含義。咱們在 JavaScript 中稱之爲「對象」的只是複合數據類型,沒有任何基於類的編程或 Alan Kay 所說的消息傳遞的含義。
在 JavaScript 中,對象能夠而且一般必須支持封裝,消息傳遞,經過方法進行行爲共享,甚至子類多態(儘管使用委託鏈而不是基於類型的調度)。您能夠將任何函數賦給任何屬性。您能夠動態建立對象行爲,並在運行時更改對象的含義。JavaScript 還支持使用閉包進行封裝以實現隱私。但全部這些都是選擇加入的行爲。
咱們如今認爲對象只是一個複合數據結構,並不須要其餘更多的含義。可是使用這樣的對象進行編程並不會使您的代碼比使用函數編程更加的「面向對象」。
由於現代編程語言中的「對象」意味的比 Alan Kay 設想的要少得多,因此我使用「組件」而不是「對象」來描述真正的面向對象的規則。在 JavaScript 中,許多 objects 能夠由其餘代碼直接擁有和操做,可是組件應該封裝和控制它們本身的狀態。
真正的面向對象的含義:
大多數組件行爲可使用代數型數據結構進行定義。不須要繼承。組件能夠經過共用函數和模塊化導入來進行重用,而無需共享數據。
在 JavaScript 中操做 objects 或使用 class inheritance 並不意味着你在「面向對象」。可是這樣使用組件就意味着「面向對象」。但流行的用法是如何定義單詞,因此也許咱們應該放棄面向對象並將其稱爲「面向消息的編程(MOP)」而不是「面向對象的編程(OOP)」?
拖把被用來清理垃圾是一種巧合嗎?
在大多數現代軟件中,有一些負責管理用戶交互的界面,還有管理應用程序狀態(用戶數據),代碼管理系統或者網絡 I/O 的代碼。
每一個系統可能都須要長期駐留的進程,例如事件監聽器,跟蹤網絡鏈接狀態,界面元素狀態和應用程序狀態等。
好的 MOP 意味着系統是經過消息調度與其餘組件通訊,不是各個系統去直接操縱彼此的狀態。當用戶單擊保存按鈕時,可能會調度「保存」消息,應用程序狀態組件可能會將其解析並轉發到狀態更新處理程序(例如純縮減器函數)。也許在狀態更新以後,狀態組件可能會向界面組件發送一個「狀態更新」的消息,而界面組件又將解析狀態,協調界面的哪些部分須要更新,並將更新後的狀態轉發給處理界面的子組件。
同時,網絡鏈接組件可能正在監視用戶與網絡上另外一臺計算機的鏈接,偵聽消息以及調度更新狀態以便在遠程計算機上保存數據。它在內部保持網絡心跳計時器,不管當前鏈接是在線仍是離線。
這些系統不須要知道系統其餘部分的細節。只須要關注自身的模塊化問題。系統組件是可分解和可從新組合的。它們實現標準化接口,以便它們可以相互操做。只要接口知足,您就能夠用使用其餘實現方式來替換當前的方式,或在不一樣的事物之間使用相同的消息通訊。你甚至能夠在運行時進行切換,一切都會照常工做。
同一個軟件系統的組件甚至都不須要位於同一臺機器上。系統能夠是分佈式的。網絡存儲可能會在分佈式存儲系統中共享數據,好比 IPFS,所以用戶不會依賴任何特定計算機的運行情況來確保其數據的安全,以避免遭受來自黑客的安全威脅。
面向對象部分受到 Arpanet 的啓發,Arpanet 的目標之一是構建一個分佈式網絡,能夠抵禦像原子彈那樣的攻擊。根據 DARPA 總監 Stephen J. Lukasik 在 Arpanet 開發期間所說(「爲何創建 Arpanet」):
「咱們的目標是開發新的計算機技術,以知足軍事上抵抗核威脅的須要,實現對美國核力量的控制,並改善軍事戰術和管理決策。」
注 Arpanet 的主要推進力是方便而不是核威脅,其明顯的防護優點是後來提出的。ARPA 使用三個獨立的計算機終端與三個獨立的計算機研究項目進行通訊。Bob Taylor 想要一個單獨的計算機網絡將每一個項目與其餘項目鏈接起來。
一個好的 MOP 系統能夠在應用程序運行時使用可熱插拔的組件來共享互聯網的健壯性。若是用戶在使用手機時由於進入一條隧道而發生離線,它能夠繼續工做。若是颶風將其中一個數據中心的電源摧毀,它仍然能夠繼續運行。
如今是時候讓軟件世界放棄失敗的類繼承實驗了,轉而接受最初定義面向對象時所秉承的數學和科學精髓。
如今是時候開始構建更靈活,更具彈性,更容易組合的軟件了,讓 MOP 和函數編程協調工做。
注意:MOP 的首字母縮略詞已被用於描述「面向監視的編程」,OOP 不太可能的會悄無聲息的消失。
若是 MOP 沒有成爲編程術語,請不要沮喪。 在你的 OOP 基礎上開始 MOP。
EricElliottJS.com 的會員能夠觀看函數編程的相關視頻。若是您還不是會員,請今天註冊。
Eric Elliott 是 「編程 JavaScript 應用程序」(O'Reilly)的做者,軟件導師平臺 DevAnywhere.io 的聯合創始人。他是不少軟件的貢獻者, Adobe Systems,Zumba Fitness,華爾街日報,ESPN,BBC ,還和頂級的唱片藝人合做,包括 Usher、Frank Ocean 和 Met 。
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到掘金翻譯計劃對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的本文永久連接即爲本文在 GitHub 上 的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。