拥抱原型面向对象编程

403 查看

来源:IBM DeveloperWorks

简介: JavaScript 是最低级的 Web 编程接口,随处可见。随着 Web 日益成为日常生活的一部分,JavaScript 也开始变得备受关注。JavaScript 是一个经常遭到误解的语言,被认为是一种玩具语言或者一种 “不成熟的 Java™ 语言”。JavaScript 最饱受非议的特性之一是它的原型对象系统。尽管不可否认 JavaScript 是存在一些缺陷,但原型对象系统并不在其内。在本文中,我们将了解功能强大、简洁、典雅的 JavaScript 原型的面向对象编程。

对象的世界

当您开始新的一天时(开车去上班,坐在办公桌前执行一个任务,吃一顿饭,逛逛公园),您通常可以掌控您的世界,或者与之交互,不必了解支配它的具体物理法则。您可以将每天面对的各种系统看作是一个单元,或者是一个对象。不必考虑它们的复杂性,只需关注您与它们之间的交互。

历史

Simula 是一种建模语言,通常被认为是第一个面向对象 (Object-oriented, OO) 的语言,随后出现的此类语言包括 Smalltalk、C++、Java 和 C#。那时,大多数面向对象的语言是通过 来定义的。后来,Self 编程语言(一个类似 Smalltalk 的系统)开发人员创建了一种可替代的轻量级方法来定义这类对象,并将这种方法称为基于原型的面向对象编程或者原型对象编程。

终于,使用一种基于原型的对象系统将 JavaScript 开发了出来,JavaScript 的流行将基于原型的对象带入了主流。尽管许多开发人员对此很反感,不过仔细研究基于原型的系统,就会发现它的很多优点。

面向对象的编程 (Object-oriented, OO) 试图创建工作原理相似的软件系统,面向对象编程是一个功能强大的、广泛流行的、用于软件开发的建模工具。 面向对象编程之所以流行,是因为它反映了我们观察世界的方法:将世界看作是一个对象集合,可与其他对象进行交互,并且可以采用各种方式对其进行操作。面向对象编程的强大之处在于其两个核心原则:

封装允许开发人员隐藏数据结构的内部工作原理,呈现可靠的编程接口,使用这些编程接口来创建模块化的、适应性强的软件。我们可以将信息封装视为信息隐藏。

继承增强封装功能,允许对象继承其他对象的封装行为。我们可以将信息继承视为是信息共享。

这些原则对于大多数开发人员来说是众所周知的,因为每个主流编程语言都支持面向对象编程(在很多情况下是强制执行的)。尽管所有面向对象语言都以这样或那样的形式支持这两个核心原则,但多年来至少形成了 2 种定义对象的不同方法。

在本文中,我们将了解原型对象编程和 JavaScript 对象模式的优势。

javascript logo

什么是 Prototypo?类和原型的关系

 提供对象的抽象 定义,为整个类或对象集合定义了共享的数据结构和方法。每个对象都被定义为其类的一个实例。类还有根据其定义和(可选)用户参数来构造类对象的责任。

一个典型的示例是 Point 类及其子类 Point3D,用来分别定义二维点和三维点。清单 1 显示了 Java 代码中的类。

清单 1. Java Point 类

和通过类来定义对象相比,原型对象系统支持一个更为直接的对象创建方法。例如,在 JavaScript 中,一个对象是一个简单的属性列表。每个对象包含另一个父类或原型 的一个特别引用,对象从父类或原型中继承行为。您可以使用 JavaScript 模拟 Point 示例,如清单 2 所示。

清单 2. JavaScript Point 类

传统对象系统和原型对象系统有本质的区别。传统对象被抽象地 定义为概念组的一部分,从对象的其他类或组中继承一些特性。相反,原型对象被具体地 定义为特定对象,从其他特定对象中继承行为。

因此,基于类的面向对象语言具有双重特性,至少需要 2 个基础结构:类和对象。由于这种双重性,随着基于类的软件的发展,复杂的类层次结构继承也将逐渐开发出来。通常无法预测出未来类需要使用的方法,因此,类层次结构需要不断重构,让更改变得更轻松。

基于原型的语言会减少上述双重性需求,促进对象的直接创建和操作。如果没有通过类来束缚对象,则会创建更为松散的类系统,这有助于维护模块性并减少重构需求。

直接定义对象的能力将会加强和简化对象的创建和操作。例如,在清单 2 中,仅用一行代码即可声明您的 point 对象: var point = { x: 0, y: 0 };。仅使用这一行代码,就可以获得一个完整的工作对象,从 JavaScript Object.prototype(比如 toString 方法)继承行为。要扩展对象行为,只需使用 point 将另一个对象声明为其原型。相反,即使最简洁的传统面向对象语言,也必须先定义一个类,然后在获得可操作对象之前将其实例化。要继承有关行为,可能需要定义另一个类来扩展第一个类。

原型模式理论上比较简单。作为人类,我们往往习惯于从原型方面思考问题。例如,Steve Yegge 在博客文章 “The Universal Design Pattern”(请参阅 参考资料)中讨论过,以橄榄球运动员 Emmitt Smith 为例,谁拥有速度、敏捷性和剪力,谁就将成为美国国家橄榄球联盟(National Football League,NFL)所有新成员的榜样。当一个新跑步运动员 LT 发挥超常捡到球时,评论员通常会这样说:

“LT 有双 Emmitt 的腿。”

“他就像 Emmitt 一样自由穿过终点线。”

“他跑一英里只用 5 分钟!”

评论员以原型 对象 Emmitt Smith 为模型来评论新对象 LT。在 JavaScript 中,这类模型看起来如清单 3 所示。

清单 3. JavaScript 模型 

和通过类来定义对象相比,原型对象系统支持一个更为直接的对象创建方法。例如,在 JavaScript 中,一个对象是一个简单的属性列表。每个对象包含另一个父类或原型 的一个特别引用,对象从父类或原型中继承行为。您可以使用 JavaScript 模拟 Point 示例,如清单 2 所示。

清单 2. JavaScript Point 类

传统对象系统和原型对象系统有本质的区别。传统对象被抽象地 定义为概念组的一部分,从对象的其他类或组中继承一些特性。相反,原型对象被具体地 定义为特定对象,从其他特定对象中继承行为。

因此,基于类的面向对象语言具有双重特性,至少需要 2 个基础结构:类和对象。由于这种双重性,随着基于类的软件的发展,复杂的类层次结构继承也将逐渐开发出来。通常无法预测出未来类需要使用的方法,因此,类层次结构需要不断重构,让更改变得更轻松。