- Original: Classical Inheritance is Obsolete - Chapter 4. Objects - Programming JavaScript Applications
- Translated by: cssmagic
Classical Inheritance is Obsolete
類繼承已經過時了
"Those who are unaware they are walking in darkness will never seek the light."
-- Bruce Lee
不知道自己正走在黑暗中的人是永遠不會去搜尋光明的。
——李小龍
In "Design Patterns: Elements of Reusable Object Oriented Software", the Gang of Four opened the book with two foundational principles of object oriented design:
在《設計模式:可復用面向對象軟件的基礎》一書的開頭,“四人幫”就推出了面向對象設計的兩大基本原則:(譯注:該書由四位作者合著,均為國際公認的面向對象軟件領域的專家。)
-
Program to an interface, not an implementation.
面向接口編程,而非面向實現編程。
-
Favor object composition over class inheritance.
優先使用對象組合,而非繼承。
In a sense, the second principle could follow from the first, because inheritance exposes the parent class to all child classes. The child classes are all programming to an implementation, not an interface. Classical inheritance breaks the principle of encapsulation, and tightly couples the child class to its ancestors.
從某種意義上說,第二條規則可由第一條推導出來,因為繼承把父類暴露給了所有子類。子類的本質都是面向實現編程,而非面向接口。類繼承打破了封裝的原則,而且把子類和它的祖先類緊密地耦合起來了。
Think of it this way: Classical inheritance is like Ikea furniture. You have a bunch of pieces that are designed to fit together in a very specific way. If everything goes exactly according to plan, chances are high that you'll come out with a usable piece of furniture, but if anything at all goes wrong or deviates from the preplanned specification, there is little room for adjustment or flexibility. Here's where the analogy (and the furniture, and the software) breaks down: The design is in a constant state of change.
不妨從這個角度來想想: 類繼承類似於宜家的家具。你有一堆零件,它們天生需要以一種非常特定的方式來組裝。如果每個零件都嚴格按計划組裝,那么你應該會得到一件可用的家具;但如果任何零件裝錯了,或偏離了說明書的規定,那就沒有多少靈活調整的空間了。這個比喻(家具或是軟件)崩潰的原因在於:這種設計需要面對不斷的變數。
Composition is more like Lego blocks. The various pieces aren't designed to fit with any specific piece. Instead, they are all designed to fit together with any other piece, with few exceptions.
而組合更像是樂高積木。各式各樣的零件並不只能與指定的零件組合。相反,每一塊積木都被設計為可以與其它零件任意組合,幾乎沒有例外。
When you design for classical inheritance, you design a child class to inherit from a specific parent class. The specific parent class name is usually hard coded right in the child class, with no mechanism to override it. Right from the start, you're boxing yourself in -- limiting the ways that you can reuse your code without rethinking its design at a fundamental level.
當你在設計類繼承時,你會從一個特定的父類繼承出一個子類。這個特定的父類的名稱通常會在子類中寫死,而沒有任何機制可以覆蓋它。而從一開始,你就在跟自己搏斗——你限制了重用代碼的方式,而沒有在一個基本層面去反思它的設計。
When you design for composition, the sky is the limit. As long as you can successfully avoid colliding with properties from other source objects, objects can be composed and reused virtually any way you see fit. Once you get the hang of it, composition affords a tremendous amount of freedom compared to classical inheritance. For people who have been immersed in classical inheritance for years, and learn how to take real advantage of composition (specifically using prototypal techniques), it is literally like walking out of a dark tunnel into the light and seeing a whole new world of possibilities open up for you.
當你在設計組合方式的時候,那就是一片海闊天空。只要你可以成功地避免其它源對象引入的屬性沖突,對象實際上就可以以任何你認為合適的方式進行組合和重用。一旦你掌握了其中的要領,相對於類繼承,組合將賦予你極大的自由。對於那些在類繼承中已經沉浸多年的人來說,學習如何從組合中受益(尤其是使用原型方面的技巧),真的像是從一條黑暗的地道中走向光明,發現一個全新的世界正向你敞開大門。
Back to "Design Patterns". Why is the seminal work on Object Oriented design so distinctly anti-inheritance? Because inheritance causes several problems:
回到“設計模式”。為什么這本面向對象領域的著作會如此旗幟鮮明地反對繼承?因為繼承會導致以下問題:(譯注:“面向對象”即 Object Oriented,以下簡稱 OO。)
-
Tight coupling. Inheritance is the tightest coupling available in OO design. Descendant classes have an intimate knowledge of their ancestor classes.
強耦合。在 OO 設計中,繼承是所能找到的最強的耦合方式。后代類對它們的祖先類了如指掌。
-
Inflexible hierarchies. Single parent hierarchies are rarely capable of describing all possible use cases. Eventually, all hierarchies are "wrong" for new uses -- a problem that necessitates code duplication.
不靈活的層級系統。單個父類層級很難描述所有應用場景的可能性。最終,所有層級對新用法來說都是“錯誤”的——不得不引入代碼重復的問題。
-
Multiple inheritance is complicated. It's often desirable to inherit from more than one parent. That process is inordinately complex and its implementation is inconsistent with the process for single inheritance, which makes it harder to read and understand.
多重繼承十分復雜。從多個父類繼承有時會很誘人。這種方法非常復雜,並且它的實現與單繼承的方式不一致,這將導致它難於閱讀和理解。
-
Brittle architecture. Because of tight coupling, it's often difficult to refactor a class with the "wrong" design, because much existing functionality depends on the existing design.
脆弱的架構。由於強耦合的存在,通常很難對一個使用了“錯誤”設計的類進行重構,因為有太多既有功能依賴這些既有設計。
-
The Gorilla / Banana problem. Often there are parts of the parent that you don't want to inherit. Subclassing allows you to override properties from the parent, but it doesn't allow you to select which properties you want to inherit.
大猩猩與香蕉問題。父類總會有某些部分是你不想繼承的。子類允許你覆蓋父類的屬性,但它不允許你選擇哪些屬性是你想繼承的。
These problems are summed up nicely by Joe Armstrong in "Coders at Work", by Peter Siebel:
這些問題都已經被 Joe Armstrong 很好地總結在了《Coders at Work》一書中,該書由 Peter Siebel 聯合執筆。
“The problem with object-oriented languages is they've got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.”
面向對象語言與生俱來的問題就是它們與生俱來的這一整個隱性環境。你想要一根香蕉,但你得到的是一頭手里握着香蕉的大猩猩,以及整個叢林。
-- Joe Armstrong
Inheritance works beautifully for a short time, but eventually the app architecture becomes arthritic. When you've built up your entire app on a foundation of classical inheritance, the dependencies on ancestors run so deep that even reusing or changing trivial amounts of code can turn into a gigantic refactor. Deep inheritance trees are brittle, inflexible, and difficult to extend.
在短時間內,繼承會工作得很完美,但最終,應用的架構會變得僵硬遲緩。當你已經在一個類繼承體系上建立起你的整個應用之后,由於祖先類的依賴關系是如此之深,以致於重用或改變一些細節末節的代碼都將導致一場大規格的重構。深度繼承樹是脆弱的、不靈活的、難於擴展的。
More often than not, what you wind up with in a mature classical OO application is a range of possible ancestors to inherit from, all with slightly different but often similar configurations. Figuring out which to use is not straightforward, and you soon have a haphazard collection of similar objects with unexpectedly divergent properties. Around this time, people start throwing around the word "rewrite" as if it's an easier undertaking than refactoring the current mess.
多半時候,在一個成熟的基於類的 OO 應用程序中,你最終得到的是一堆可以用來繼承的祖先類,它們都有着細微的差別,但又常常具有相似的配置。找出一個適用的祖先類並不容易,而且你很快會擁有一堆雜亂無章的相似對象,這些對象又擁有一堆雜亂無章的屬性。到了這個時候,人們開始拋出“重寫”這個詞,仿佛這樣會比重構眼下這一團槽更容易些。
Many of the patterns in the GoF book were designed specifically to address these well-known problems. In many ways, the book itself can be read as a critique of the shortcomings of most classical OO languages, along with the accompanying lengthy work-arounds. In short, patterns point out deficiencies in the language. You can reproduce all of the GoF patterns in JavaScript, but before you start using them as blueprints for your JavaScript code, you'll want to get a good handle on JavaScript's prototypal and functional capabilities.
“四人幫”書里的很多模式都特別適合講解這些著名的問題。在很多方面,這本書讀起來就像是對大多數基於類的 OO 語言缺陷的一種批判,同時書中也提供了冗長的變通解決方案。簡單來說,設計模式指出了語言中的短板。你可以用 JavaScript 重現書中的所有模式,但在你准備以它們為基礎來構建你的 JavaScript 代碼,你不妨先掌握好 JavaScript 的原型式和函數式編程能力。
For a long time, many people were confused about whether JavaScript is truly object oriented, because they felt that it lacked features from other OO languages. Setting aside the fact that JavaScript handles classical inheritance with less code than most class-based languages, coming to JavaScript and asking how to do classical inheritance is like picking up a touch screen mobile phone and asking where the rotary dial is. Of course people will be amused when the next thing out of your mouth is, "if it doesn't have a rotary dial, it's not a telephone!"
在很長一段時間內,很多人都對 JavaScript 是否是一門真正的 OO 語言感到疑惑,因為他們感覺它缺少其它 OO 語言的一些特性。暫不提 JavaScript 只需要(與大多數基於類的語言相比)更少的代碼就可以搞定類繼承;剛接觸 JavaScript 就問如何實現類繼承,就好像撿起一部觸屏手機然后問別人它的撥號轉盤在哪里。當然,如果你回答“它沒有撥號轉盤,它不是電話機”,那些人也許會被逗樂。
JavaScript can do most of the OO things you're accustomed to in other languages, such as inheritance, data privacy, polymorphism, and so on. However, JavaScript has many native capabilities that make some classical OO features and patterns obsolete. It's better to stop asking "how do I do classical inheritance in JavaScript?", and start asking, "what cool new things does JavaScript enable me to do?"
JavaScript 幾乎可以實現所有其它語言所能做到的 OO 行為,比如繼承、數據私有化、多態等等。然而,JavaScript 原生具備的很多能力就足以讓一些基於類的 OO 特性和模式變得過時。所以最好別再問“怎樣在 JavaScript 中實現類繼承”,而應該問“我在 JavaScript 中可以做哪些不一樣的、超酷的事情?”
[!]
I wish I could tell you that you'll never have to deal with classical inheritance in JavaScript. Unfortunately, because classical inheritance is easy to mimic in JavaScript, and many people come from class-based programming backgrounds, there are several popular libraries which feature classical inheritance prominently, including Backbone.js, which you'll have a chance to explore soon. When you do encounter situations in which you're forced to subclass by other programmers, keep in mind that inheritance hierarchies should be kept as small as possible. Avoid subclassing subclasses, remember that you can mix and match different code, reuse styles, and things will go more smoothly.
[!]
但願你可以明白,你永遠不需要在 JavaScript 中使用類繼承模式。但不幸的是,由於類繼承在 JavaScript 中很容易模似,而且有太人來自於基於類的編程背景,導致很一些流行的類庫特意引入了類繼承的特性,這其中包括 Backbone.js,我們后面會有機會詳細了解它。如果你確實遇到不得不使用其它程序員寫的子類的情況,請牢記繼承層級應該控制得盡可能少。要避免創建子類的子類,別忘了你可以混合並匹配不同的代碼、重用樣式,然后事情會變得更加順利。