《JavaScript应用程序设计》一一3.1 过时的类继承

简介:

本节书摘来华章计算机出版社《JavaScript应用程序设计》一书中的第3章,第3.1节,作者:Eric Elliott 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

3.1 过时的类继承

不知道自己正走在黑暗中的人是永远不会去搜寻光明的。
——李小龙
在《设计模式:可复用面向对象软件的基础》一书的首章,“四人帮”向我们介绍了面向对象设计的两大基本原则:

  1. 面向接口编程,而非面向实现编程。
  2. 优先使用(对象)组合,而非(类)继承。
    从某种角度上说,第二条原则是从第一条中派生而来,因为父类通过“继承”将其内部结构直接暴露给了所有子类,而子类从本质上来说都是在面向实现编程,并非面向接口。类继承违反了代码封装的原则,它使得子类与其祖先之间产生了紧耦合。

我们可以将类继承比作是宜家的家具,假设你买来一堆家具零部件,它们需要按照一定方式组装在一起,如果每个零部件都严格参照说明书中的描述来组装,那么大部分情况下你的家具不会有问题;但如果碰巧某个零部件本身存在缺陷,或者在组装时没有按照说明书中的操作来做,则家具可能就此就报废了。如果把这个例子放在软件开发领域中会得到这样一条结论:代码设计总是在不停地变化。
组合则看起来更像是乐高积木,没有谁规定说积木非得要搭建成什么样子,所有积木之间都可以做自由组合,从而形成不同形状、不同大小的物体(玩具)。
当你在为类继承做代码设计时,你总是在思考如何从具体父类中派生出子类。父类大都会将名称直接硬编码进子类,完全不给你做重写的机会。使用类继承等于一开始就将自己囚禁了起来,限制了代码复用性不说,关键会导致你不会从一个基本层面上反思代码设计。
而当你在为组合做代码设计时,你只需要留心多个对象间的属性是否会有冲突就可以了,总体上来说整个设计的过程中不会有太多的约束,对象可以根据你的设计做任意组合与重用。一旦你掌握了组合的要领,相比类继承来说,在代码设计上将获得更大的自由度。对于那些常年在类继承体系下工作的程序员来说,学习与掌握组合(尤其是使用原型技术)就像是在漆黑狭窄的隧道中,发现远方的一束光亮一样,迎接他的是一个充满了各种可能性的崭新世界。
我们先暂时回到《设计模式》这本书中来,为什么这本面向对象领域的著作会如此旗帜鲜明地反对继承?因为继承会引发下列问题:
对象或模块间的强耦合:
在面向对象程序设计中,继承是最容易导致强耦合的代码复用方式,因为派生类对其祖先类的一切都十分了解。
继承层级结构缺乏灵活性:
单一父类的继承层级结构本身就很难覆盖到所有功能用例,而且对新加入的功能用例的适配性也很差,最终导致代码彼此重复。
多重继承同时带来了代码复杂度:
让子类继承多个父类,这从代码复用的角度看来十分具有诱惑性,但它实现的过程不仅非常复杂,而且与单继承有很大的差异性,这常常也是多重继承的代码结构非常晦涩的原因。
架构变得极为不稳定:
一般情况下,由于强耦合的存在,使得你很难对一个采用了“错误”设计的类进行重构,因为有太多的现有功能依赖它。
大猩猩与香蕉问题的产生:
在父类中,总会有一些特性是你不想让子类继承的,子类可以重写父类的部分特性,但它决定不了哪些特性是你真正想要从父类中继承的。
《Coders at Work》一书对绝大部分类继承所引发的问题做了总结梳理,该书由Joe Armstrong与Peter Siebel联合执笔(http://www.codersatwork.com/)
面向对象语言的问题,就在于它本身所持有的隐性语言环境。好比说你想要一根香蕉,它却把握着香蕉的大猩猩扔给你,而有时甚至会是整个森林。
继承只能够带来一时的效率提升,而到了后期应用的架构,就像是患了关节炎一样,变得行动迟缓起来。当你将整个应用搭建在类继承体系上时,任何对代码的细微修改都可能导致大范围的代码重构,这是因为上层祖先类间的依赖关系往往是非常紧密的。继承树过深,会导致代码丧失灵活性、易碎且难于扩展。
一般来说,在一个严重依赖于类继承的应用程序中,会有大量祖先类的存在,这些祖先类相互间略有差异,但是配置却很相像,这使得分辨它们各自的用途成了一件难事,而且应用很快便被一群看起来极为相似,行为却十分迥异的实例对象所充斥。“重写”一词多半就是在这个时候被抛出来的,人们仿佛觉得这样做,会比代码重构来的要便捷。
《设计模式》一书中的绝大部分内容其实就是针对上述这些问题的解决方案,但是这些方案有时会显得较为烦琐,反而给程序引入了更多的代码复杂度。有趣的是,这本书的内容读起来会让人感觉像是对所有面向对象语言的一次集体揭短。虽然说你可以将书中的所有模式直接搬进JavaScript中,不过建议你在使用它们之前,最好先把JavaScript中原型与函数的概念了解清楚。
很多人一直以来都不太明白JavaScript究竟是不是一门真正的面向对象语言,因为他们认为,JavaScript相比其他面向对象语言来说,总有些缺胳膊少腿的感觉。在这里,我们抛开JavaScript 可以模拟类继承的能力(与多数基于类的语言相比代码要更为精简)不谈,如果你刚接触JavaScript就开始寻思着如何引入类继承,就好比是拿着智能触屏手机问别人拨号转盘在哪儿一样,此时如果你一本正经地告诉别人:“它根本就不是电话机,因为它没有拨号转盘”,人们会认为你在搞笑。
如继承、数据隐蔽性、多态等,这些原本出自于其他语言的面向对象特性,在JavaScript中都可以被模拟,但JavaScript与生俱来的一些语言特性反而会让人们觉得,他们可以在JavaScript中完全废弃这些基于类继承的编码模式。所以别再去纠结诸如“我如何在JavaScript中实现类继承”之类的问题了,反而你更应该去思考:“JavaScript能够带给我哪些全新的特性?”。
警告: 我希望你能明白一点,你永远不需要在JavaScript中使用类继承。说是这么说,但由于类继承在JavaScript 中非常容易被模拟,加之有很多人都有传统面向对象语言的编程背景,所以导致市面上许多类库都有意引入了类继承的概念,这其中就包括 Backbone.js,我会在后续章节对它进行介绍。如果你在编码中不得不使用类继承,那么请严格控制继承的层级深度。有很多代码复用方式能够代替继承,它们往往具有更好的延展性。

相关文章
|
JavaScript 前端开发 Java
深入JS面向对象(原型-继承)(一)
深入JS面向对象(原型-继承)
30 0
|
26天前
|
JavaScript
js开发:请解释什么是ES6的类(class),并说明它与传统构造函数的区别。
ES6的类提供了一种更简洁的面向对象编程方式,对比传统的构造函数,具有更好的可读性和可维护性。类使用`class`定义,`constructor`定义构造方法,`extends`实现继承,并可直接定义静态方法。示例展示了如何创建`Person`类、`Student`子类以及它们的方法调用。
21 2
|
28天前
|
JavaScript 前端开发
js开发:请解释原型继承和类继承的区别。
JavaScript中的原型继承和类继承用于共享对象属性和方法。原型继承利用原型链查找属性,节省内存但不支持私有成员。类继承通过ES6的class和extends实现,支持私有成员但占用更多内存。两者各有优势,适用于不同场景。
18 0
|
1月前
uni-app 65egg.js聊天类chat.js封装(二)
uni-app 65egg.js聊天类chat.js封装(二)
25 1
|
3月前
|
JavaScript
|
3月前
|
JavaScript 前端开发
原型继承在 JavaScript 中是如何工作
原型继承在 JavaScript 中是如何工作
20 0
|
3月前
|
JavaScript 前端开发
JS实现继承的6种方式
JS实现继承的6种方式
|
1月前
|
设计模式 JavaScript 前端开发
JavaScript中继承的优缺点
JavaScript中继承的优缺点
13 3
|
1月前
|
JavaScript 前端开发
如何在 JavaScript 中实现继承?
如何在 JavaScript 中实现继承?
11 2
|
1月前
|
JavaScript 前端开发
js继承的超详细讲解:原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合式继承、class继承
js继承的超详细讲解:原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合式继承、class继承
51 0