javascript
在JavaScript里写类层次结构?别那么做!
從理論上講,JavaScript并沒有類。在實踐中,下面的代碼片段被廣泛認為是JavaScript“類”的一個例子:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function Account () { ??this._currentBalance = 0; } Account.prototype.balance = function () { ??return this._currentBalance; } Account.prototype.deposit = function (howMuch) { ??this._currentBalance = this._currentBalance + howMuch; ??return this; } // ... var account = new Account(); |
這個模式可以被拓展以提供子類:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | function ChequingAccount () { ??Account.call(this); } ChequingAccount.prototype = Object.create(Account.prototype); ChequingAccount.prototype.sufficientFunds = function (cheque) { ??return this._currentBalance >= cheque.amount(); } ChequingAccount.prototype.process = function (cheque) { ??this._currentBalance = this._currentBalance - cheque.amount(); ??return this; } |
這些類和子類擁有類的大部分特性,就像Smalltalk語言中類的特性一樣:
- 類負責創建對象且用參數來初始化它們(比如當前余額)。
- 類管理和擁有函數(方法),對象委托函數(方法)來處理它們的類(還有超級類)。
- 函數(方法)直接操作對象的屬性。
這種模式在JavaScript文化中變得根深蒂固,ECMAScript-6——即將對JavaScript進行大修改的標準,提供了一些“糖衣語法”,因此在我們寫類和子類的時候不用手工寫完全部的模式內容。這對語義并沒有太大的更改,?一切還是如我們看到的一樣在后臺運行得很好。
當然,Smalltalk是四十多年前發明的,在這四十多年里,我們學會了關于哪些能或不能在面向對象程序設計中使用的很多問題。不幸的是,這種模式為做不了的事高興,而卻掩蓋或忽略能做的事情。
更不幸的是,即將到來的糖衣語法并沒有解決關于類的任何問題,只解決了這些問題:“我希望能少敲點代碼”這樣的問題,或者對于新程序員來說“我不理解這些運動的部件實際上是如何工作的,所以我可以寫錯代碼了,有沒有更簡單的方法來寫這些代碼呢?”。
層次結構的語義問題
在語義層面,類是本體的構建單元。下圖的內容通常有有效的:
基于類的面向對象編程的背后思想是將我們的對象知識分類(注意這個詞)成樹。在頂層的是有關所有對象的最一般知識,順著樹下來,我們得到越來越多的關于對象特定的類的特定知識,比如代表VisaDebit賬號的對象。
僅僅在編程上而已,真實的世界并不是那樣。確實不是那樣的。在形態學中,比如我們有,企鵝像鳥類那樣游泳,蝙蝠像哺乳動物那樣飛,像鴨嘴獸那樣的單孔目動物是卵生的哺乳動物。
事實證明我們有意義的領域(比如形態學或銀行業務)的行為并沒有能分類成很好的樹,它形成一個有向非循環圖。如果我們站在其中,那么它就是一片叢林。
此外,在樹形本體頂端構建軟件的想法將要破滅,即使我們的知識能很整齊的構成一棵樹。本體論不用來構建真實的世界,他們通過觀察來描繪這個世界。隨著認知不斷的增長,我們也在不斷的更新自己的本體論,有時能移動周圍的一切。
在軟件中,這具有難以置信的破壞力:移動周圍所有的東西會破壞所有的東西。在真實世界中,如果我們重新排列本體,卑微的鴨嘴獸并不介意,因為我們并沒有用本體論來構建澳大利亞,只是用來描述我們的發現而已。
通過觀察像銀行賬號這樣的事物來構建本體論是合理的。這種本體對于需求、用例、測試等來說是有用的。但這并不意味著它對實現銀行賬戶代碼書寫有幫助。
類層次結構是錯誤的語義模型,四十年的經驗智慧讓他們有更好的辦法構建程序。
封裝
這些都是語義問題。讓我們來談談工程方面的問題,讓我們來處理類就好像我們并不關心它們是否代表真實世界中的一些知識,讓我們相信類僅僅只是讓我們程序能夠正常運行的一個工具而已。那么這些還是問題嗎?
類層次結構是一個問題,即使我們都想要做的是用它們來實現一些行為。程序有三個重要的規則:
1.程序必須易于編寫
2.程序必須易于理解
3.程序必須易于修改
類要權衡所有這3個重要的規則,但類層次結構對遵從理解和改變程序是有害的,因為這種方式導致了封裝問題。
封裝是面向對象編程一個核心的原則。(其它的編程風格,如函數式編程,也注重封裝,即使以不同的方式實現)。在面向對象編程中,封裝是由對象的私有狀態和方法的公共接口來實現的。
JavaScript并不強制要求私有狀態,但能很容易寫出封裝很好的程序:只需避免一個對象直接操作另一個對象的屬性。Smalltalk發明了四十多年后,這是一個很好理解的原則。
顯然,代碼間將會有依賴性。A將依賴B,B將依賴C,且這些依賴是具有傳遞性的,所有A依賴B,那么A同時也依賴于C。封裝并沒有消除依賴關系,但確實還是限制了依賴的范圍:如果我們改變B和/或C,假如我們沒有改變或移動A調用的外部可視的方法,那么A就不會被破壞。
到目前為止,一切都很好。或至少如果A、B、C是對象和/或方法。例如:
| 1 2 3 4 5 6 7 | function depositAndReturnBalance(account, amount) { ??return account.deposit(amount).balance(); } var account = new Account(); depositAndReturnBalance(account, 100) ??//=> 100 |
很明顯depositAndReturnBalance通過一個對象的傳遞實現了.deposit?和.balance方法。但這不依賴于這些方法是如何實現的:我們可以這樣來寫Account,也能得到相同功能:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | function Account () { ??this._transactionHistory = []; } Account.prototype.balance = function () { ??return this._transactionHistory.reduce(function (acc, transaction) { ????return acc + transaction; ??}, 0); } Account.prototype.deposit = function (howMuch) { ??this._transactionHistory.unshift(howMuch) ??return this; } function depositAndReturnBalance(account, amount) { ??return account.deposit(amount).balance(); } var account = new Account(); depositAndReturnBalance(account, 100) ??//=> 100 |
.deposit?和.balance完全不同的實現方法,但depositAndReturnBalance并沒有依賴于這些實現方法。
所以,類給我們提供了封裝“賬戶余額”實現的一種方法。太棒了!這有什么問題嗎?
父類沒有被封裝
我們說過當實體只包含對象和/或方法時,封裝能在JavaScript中實現。但是類呢?
事實證明,類之間的層級關系是沒有封裝的。這是因為類之間沒有通過明確定義的方法接口來進行關聯,而是各自“隱藏”其內部狀態。
這是ChequingAccount子類實現.process函數的一種方法:
| 1 2 3 4 | ChequingAccount.prototype.process = function (cheque) { ??this._currentBalance = this._currentBalance - cheque.amount(); ??return this; } |
如果我們用交易記錄代替當前余額來重寫Account類,這會破壞ChequingAccount的代碼。在JavaScript(和同一家庭的其它語言)中類和子類共享對象私有屬性的訪問權。如果沒有細致檢查每一個子類和每一處調用子類的代碼,那么改變Account的實現細節是不太可能的,因為改變私有屬性將破壞它們。
當然,我們知道代碼間是存在關聯的,所有子類依賴父類這并不讓我們感到驚訝。但不同的是這種關聯是不受方法和接口范圍影響的。我們沒有封裝。
這個問題并不是一個新的問題。這很好理解,它甚至有一個名字:叫做脆弱的基類問題。改變靠近繼承樹頂端的類會產生深遠的影響,且這種影響是呈數量級排列的,還是因為沒有封裝。
類繼承會讓程序變得難以修改且脆弱。
展望未來
JavaScript是在1995年首次露面的,約Smalltalk首次發布后的15年。從那以后的20年里,我們學習了很多關于JavaScript好的壞的東西,同時我們也學到了面向對象編程的很多好方法和壞主意。
很明顯,我們應該回顧和借鑒之前發生的事情。好的理念,比如封裝,函數屬于第一類對象,委托,特性和構成應該被包含進來且需要提升。新的理念,比如promises模式,應該得以發展。
人家經常說“JavaScript不是Ruby”,因為它是基于原型的,不是基于類的。這確實是真的,但如果我們重造,那么優勢將會丟失,如果將40年前創造的并一直延用的理念棄用,這很不好。
所以當有人讓你陳述如何寫一個類層次結構的話,請告訴它們:別那么做!
(在?hacker?news,?/r/javascript, 和?/r/programming上參與討論)
轉載于:https://www.cnblogs.com/xiaochao12345/p/3672563.html
總結
以上是生活随笔為你收集整理的在JavaScript里写类层次结构?别那么做!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c标签 if else
- 下一篇: 不用中间变量交换两个变量的值