不想错过更多好文?请点击上面的 “CSS魔法” 订阅公众号。 |
不知道自己正走在黑暗中的人是永远不会去搜寻光明的。
——李小龙
在《设计模式:可复用面向对象软件的基础》一书的开头,“四人帮” 就推出了面向对象设计的两大基本原则:(译注:该书由四位作者合著,均为国际公认的面向对象软件领域的专家。)
面向接口编程,而非面向实现编程。
优先使用对象组合,而非继承。
从某种意义上说,第二条规则可以从第一条推导出来,因为 “继承” 把父类暴露给了所有子类。子类的本质都是面向实现编程,而非面向接口。类继承打破了封装的原则,它把子类和它的祖先类紧密地耦合起来了。
不妨从这个角度来想想: 类继承类似于宜家的家具。你有一堆零件,它们天生需要以一种非常特定的方式来组装。如果每个零件都严格按计划组装,那么你应该会得到一件可用的家具;但如果任何一个零件装错了,或者偏离了说明书的规定,那就没有多少可以灵活调整的空间了。这种方式(家具或是软件)的失败原因在于:这种设计无法承受持续不断的变数。
而组合更像是乐高积木。各式各样的零件并不只能与指定的零件组合。相反,每一块积木都被设计为可以与其它零件任意组合,几乎没有例外。
当你在设计类继承时,你会从一个特定的父类继承出一个子类。这个特定的父类的名称通常会在子类中写死,而没有任何机制可以覆盖它。而从一开始,你就在跟自己搏斗——你限制了重用代码的方式,而没有从一个基本层面去反思它的设计。
当你在设计组合方式的时候,那就是一片海阔天空。只要你可以成功地避免其它 “源对象” 引入的属性冲突,对象实际上就可以以任何你认为合适的方式进行组合和重用。一旦你掌握了其中的要领,相对于类继承,组合将赋予你极大的自由。对于那些在类继承中已经沉浸多年的人来说,学习如何从组合中受益(尤其是使用原型方面的技巧),真的像是从一条黑暗的地道中走向光明,发现一个全新的世界正向你敞开大门。
回到《设计模式》。为什么这本面向对象领域的著作会如此旗帜鲜明地反对继承?因为继承会导致以下问题:(译注:“面向对象” 即 Object Oriented,以下简称 OO。)
强耦合。在 OO 设计中,继承是所能找到的最强的耦合方式。后代类对它们的祖先类了如指掌。
层级系统不灵活。单个父类层级很难描述所有应用场景的可能性。最终,所有层级对新用法来说都是 “错误” 的——这必然产生代码重复。
多重继承十分复杂。从多个父类继承有时会很诱人。这种方法非常复杂,并且它的实现与单继承的方式不一致,这将导致它难于阅读和理解。
架构脆弱。由于强耦合的存在,通常很难对一个使用了 “错误” 设计的类进行重构,因为有太多既有功能依赖这些既有设计。
大猩猩与香蕉问题。父类总会有某些部分是你不想继承的。子类允许你覆盖父类的属性,但它不允许你选择哪些属性是你想继承的。
这些问题都已经被 Joe Armstrong 很好地总结在了《Coders at Work》一书中,该书由 Peter Siebel 联合执笔。
面向对象语言与生俱来的问题就是它们与生俱来的这一整个隐性环境。你想要一根香蕉,但你得到的是一头手里握着香蕉的大猩猩,以及整个丛林。
——Joe Armstrong
在短时间内,继承会工作得很完美,但最终,应用的架构会变得僵硬迟缓。当你已经在一个类继承体系上建立起你的整个应用之后,由于祖先类的依赖关系是如此之深,以致于重用或改变一些细枝末节的代码都将导致一场大规格的重构。深度继承树是脆弱的、不灵活的、难于扩展的。
多半时候,在一个成熟的基于类的 OO 应用程序中,你最终会得到一堆可以用来继承的祖先类,它们之间有着细微的差别,但又常常具备相似的配置。找出一个适用的祖先类并不容易,而且你很快会拥有一堆杂乱无章的相似对象,这些对象又拥有一堆杂乱无章的属性。到了这个时候,人们开始抛出 “重写” 这个词,仿佛这样会比重构眼下这一团槽更容易些。
“四人帮” 书里的很多模式都特别适合讲解这些比较典型的问题。很多时候,这本书读起来就像是对众多基于类的 OO 语言缺陷的一种批判,同时书中也提供了冗长的变通解决方案。简单来说,设计模式指出了语言中的短板。你可以用 JavaScript 重现书中的所有模式,但在你准备以它们为基础来构建你的 JavaScript 代码之前,不妨先掌握好 JavaScript 的原型式和函数式编程能力。
在很长一段时间内,很多人都对 JavaScript 是否是一门真正的 OO 语言感到疑惑,因为他们感觉它缺少其它 OO 语言的一些特性。这里暂且不提 JavaScript 只需要(与大多数基于类的语言相比)更少的代码就可以搞定类继承;实际上,刚接触 JavaScript 就问如何实现类继承,就好像捡起一部触屏手机然后问别人它的拨号转盘在哪里。当然,如果你回答 “它没有拨号转盘,它不是电话机”,那些人也许会被逗乐。
JavaScript 几乎可以实现所有其它语言所能做到的 OO 行为,比如继承、数据私有化、多态等等。然而,JavaScript 原生具备的很多能力就足以让一些基于类的 OO 特性和模式相形见绌。所以最好别再问 “怎样在 JavaScript 中实现类继承”,而应该问 “我在 JavaScript 中可以做哪些特别的、超酷的事情?”
⚠️ 但愿你可以明白,你永远不需要在 JavaScript 中使用类继承模式。但不幸的是,由于类继承在 JavaScript 中很容易模似,而且有太多人来自于基于类的编程背景,导致很一些流行的类库特意引入了类继承的特性,这其中包括 Backbone.js,我们会在后面详细了解它。如果有时候你不得不使用其它程序员写的子类,请务必牢记继承层级应该控制得尽可能少。要避免创建子类的子类,别忘了你可以混合并匹配不同的代码、重用样式,然后一切会变得更加顺利。
本文摘自《Programming JavaScript Applications》一书的第 4.1 节 “Classical Inheritance is Obsolete”。
一开始把标题翻译成了 “传统的继承……”。译到一半时才反应过来,这里的 “classical” 不仅是字典里说的 “经典的、传统的、古典的”,也可以理解为 “class”(类)的形容词形态。算是一语双关吧。
这篇文章译得有些艰难。一方面全文都是纯理论,一行代码都没有出现。另一方面是因为我本身对 OO 没有深入研究,也没有其它 OO 语言的背景,所以基本是直译。如果大家发现有错漏,请果断评论指出,多谢!
(题图作者:Lorenzo Scheda @ Flickr)
如果需要以中英对照的方式阅读,请点击 “阅读原文”。 |