第四章: 混合(淆)“类”的对象 - 类理论
类理论
“类/继承”描述了一种特定的代码组织和结构形式 —— 一种在我们的软件中对真实世界的建模方法。
OO 或者面向类的编程强调数据和操作它的行为之间有固有的联系(当然,依数据的类型和性质不同而不同!),所以合理的设计是将数据和行为打包在一起(也称为封装)。这有时在正式的计算机科学中称为“数据结构”。
比如,表示一个单词或短语的一系列字符通常称为“string(字符串)”。这些字符就是数据。但你几乎从来不关心数据,你总是想对数据 做事情, 所以可以 向 数据实施的行为(计算它的长度,在末尾添加数据,检索,等等)都被设计成为 String
类的方法。
任何给定的字符串都是这个类的一个实例,这个类是一个整齐的集合包装:字符数据和我们可以对它进行操作的功能。
类还隐含着对一个特定数据结构的一种 分类 方法。其做法是将一个给定的结构考虑为一个更加泛化的基础定义的具体种类。
让我们通过一个最常被引用的例子来探索这种分类处理。一辆 车 可以被描述为一“类”更泛化的东西 —— 载具 —— 的具体实现。
我们在软件中通过定义 Vehicle
类和 Car
类来模型化这种关系。
Vehicle
的定义可能会包含像动力(引擎等),载人能力等等,这些都是行为。我们在 Vehicle
中定义的都是所有(或大多数)不同类型的载具(飞机、火车、机动车)都共同拥有的东西。
在我们的软件中为每一种不同类型的载具一次又一次地重定义“载人能力”这个基本性质可能没有道理。反而,我们在 Vehicle
中把这个能力定义一次,之后当我们定义 Car
时,我们简单地指出它从基本的 Vehicle
定义中“继承”(或“扩展”)。于是 Car
的定义就被称为特化了更一般的 Vehicle
定义。
Vehicle
和 Car
用方法的形式集约地定义了行为,另一方面一个实例中的数据就像一个唯一的车牌号一样属于一辆具体的车。
这样,类,继承,和实例化就诞生了。
另一个关于类的关键概念是“多态(polymorphism)”,它描述这样的想法:一个来自于父类的泛化行为可以被子类覆盖,从而使它更加具体。实际上,相对多态允许我们在覆盖行为中引用基础行为。
类理论强烈建议父类和子类对相同的行为共享同样的方法名,以便于子类(差异化地)覆盖父类。我们即将看到,在你的 JavaScript 代码中这么做会导致种种困难和脆弱的代码。
“类”设计模式
你可能从没把类当做一种“设计模式”考虑过,因为最常见的是关于流行的“面向对象设计模式”的讨论,比如“迭代器(Iterator)”、“观察者(Observer)”、“工厂(Factory)”、“单例(Singleton)”等等。当以这种方式表现时,几乎可以假定 OO 的类是我们实现所有(高级)设计模式的底层机制,好像对所有代码来说 OO 是一个给定的基础。
取决于你在编程方面接受过的正规教育的水平,你可能听说过“过程式编程(procedural programming)”:一种不用任何高级抽象,仅仅由过程(也就是函数)调用其他函数构成的描述代码的方式。你可能被告知过,类是一个将过程式风格的“面条代码”转换为结构良好,组织良好代码的 恰当 的方法。
当然,如果你有“函数式编程(functional programming)”的经验,你可能知道类只是几种常见设计模式中的一种。但是对于其他人来说,这可能是第一次你问自己,类是否真的是代码的根本基础,或者它们是在代码顶层上的选择性抽象。
有些语言(比如 Java)不给你选择,所以这根本没什么 选择性 —— 一切都是类。其他语言如 C/C++ 或 PHP 同时给你过程式和面向类的语法,在使用哪种风格合适或混合风格上,留给开发者更多选择。
JavaScript 的“类”
在这个问题上 JavaScript 属于哪一边?JS 拥有 一些 像类的语法元素(比如 new
和 instanceof
)有一阵子了,而且在最近的 ES6 中,还有一些追加的东西,比如 class
关键字(见附录A)。
但这意味着 JavaScript 实际上 拥有 类吗?简单明了:没有。
由于类是一种设计模式,你 可以,用相当的努力(正如我们将在本章剩下的部分看到的),近似实现很多经典类的功能。JS 在通过提供看起来像类的语法,来努力满足用类进行设计的极其广泛的 渴望。
虽然我们好像有了看起来像类的语法,但是 JavaScript 机制好像在抵抗你使用 类设计模式,因为在底层,这些你正在上面工作的机制运行的十分不同。语法糖和(极其广泛被使用的)JS “Class”库费了很大力气来把这些真实情况对你隐藏起来,但你迟早会面对现实:你在其他语言中遇到的 类 和你在 JS 中模拟的“类”不同。
总而言之,类是软件设计中的一种可选模式,你可以选择在 JavaScript 中使用或不使用它。因为许多开发者都对面向类的软件设计情有独钟,我们将在本章剩下的部分中探索一下,为了使用 JS 提供的东西维护类的幻觉要付出什么代价,和我们经历的痛苦。