基于原型与基于类的继承


208

在JavaScript中,每个对象同时是一个实例和一个类。要进行继承,可以将任何对象实例用作原型。

在Python,C ++等中。有类和实例作为单独的概念。为了进行继承,您必须使用基类创建一个新类,然后该新类可用于生成派生实例。

为什么JavaScript朝这个方向发展(基于原型的面向对象)?与传统的基于类的OO相比,基于原型的OO有哪些优点和缺点?


10
JavaScript受Self的影响,Self是具有原型继承性的第一语言。那时,古典继承风靡一时,最早是在Simula中引入的。但是,经典继承太复杂了。然后,大卫·昂加(David Ungar)和兰德尔·史密斯(Randall Smith)在阅读了GEB之后顿悟了一下:“最具体的事件可以作为一类事件的一般示例。” 他们意识到,面向对象的编程不需要类。因此,塞尔夫诞生了。要知道原型继承比经典继承更好,请阅读:stackoverflow.com/a/16872315/783743 =)
Aadit M Shah 2013年

@AaditMShah什么是谁GEB
Alex

3
@Alex GEB是道格拉斯·霍夫施塔特(Douglas Hofstadter)所写的书。它是GödelEscher Bach的缩写。KurtGödel是一位数学家。埃舍尔(Escher)是一位艺术家。巴赫是一位钢琴家。
Aadit M Shah,2016年

Answers:


201

这里大约有一百个术语问题,大多数是围绕某人(不是您)试图使他们的想法听起来像是“最好的”。

所有面向对象的语言都必​​须能够处理以下几个概念:

  1. 数据封装以及对数据的关联操作,除其他外,各种操作被称为数据成员和成员函数,或者称为数据和方法。
  2. 继承性,可以说这些对象就像其他对象一样,但这些更改除外
  3. 一个对象的多态性(“许多形状”),它可以自己决定要运行的方法,以便您可以依靠语言正确地路由请求。

现在,就比较而言:

首先是整个“类”与“原型”问题。这个想法最初是从Simula开始的,在Simula中,每个类都使用基于类的方法来代表一组对象,这些对象共享相同的状态空间(读取“可能的值”)和相同的操作,从而形成一个等效类。如果回顾一下Smalltalk,由于可以打开一个类并添加方法,因此,这实际上与在Javascript中可以执行的操作相同。

后来的OO语言希望能够使用静态类型检查,因此我们有了在编译时设置固定类的概念。在开放式版本中,您具有更大的灵活性;在较新的版本中,您可以在编译器中检查某些类型的正确性,否则它们将需要测试。

在“基于类”的语言中,复制发生在编译时。在原型语言中,操作存储在原型数据结构中,并在运行时进行复制和修改。但是,抽象地讲,一个类仍然是共享相同状态空间和方法的所有对象的等效类。在原型中添加方法时,实际上是在制作新的等效类的元素。

现在,为什么呢?主要是因为它在运行时提供了一种简单,逻辑,优雅的机制。现在,要创建一个新的对象创建一个新的类,您只需要执行一个深层复制,即复制所有数据和原型数据结构。然后,您或多或少可以免费获得继承和多态性:方法查找始终包括按名称向字典请求方法实现。

最终以Javascript / ECMA脚本结尾的原因基本上是,当我们在10年前开始使用该技术时,我们所使用的功能却差强人意的计算机和复杂得多的浏览器。选择基于原型的方法意味着解释器可能非常简单,同时保留了面向对象的理想属性。


1
是的,那段文字看起来像我的意思吗?Dahl和Nyqvist提出了“类”作为具有相同方法签名的事物集合。
查理·马丁

1
这种变化是否更好?
查理·马丁

2
不,对不起,CLOS来自80年代后期的dreamsongs.com/CLOS.html来自1980 en.wikipedia.org/wiki/Smalltalk和Simula的Smalltalk,其完整对象定向来自1967-68 en.wikipedia.org/wiki/Simula
Charlie马丁

3
@Stephano,它们并没有那么不同:Python,Ruby,Smalltalk使用字典进行方法查找,而javascript和Self具有类。在某种程度上,您可能会争辩说,不同之处仅在于面向原型的语言正在公开其实现。因此,最好不要将它做成一笔大买卖:这可能更像是EMACS和vi之间的争论。
查理·马丁

21
有用的答案。+1评论中不太有用的垃圾。我的意思是说,首先使用CLOS还是Smalltalk会有所不同吗?无论如何,这里的大多数人都不是历史学家。
亚当·阿罗德

40

可以在论文《自我:简单的力量》中找到一个比较,该比较稍微偏向基于原型的方法。本文提出以下赞成原型的论点:

通过复制创建。从原型创建新对象是通过简单的操作,复制以及简单的生物隐喻克隆来完成的。从类创建新对象是通过实例化完成的,实例化包括对类中格式信息的解释。实例化类似于根据计划建造房屋。复制对我们的吸引力是比实例化更简单的隐喻。

预先存在的模块的例子。原型比类更具体,因为它们是对象的示例,而不是格式和初始化的描述。这些示例可以使用户更易于理解,从而帮助用户重用模块。基于原型的系统允许用户检查典型的代表,而不是要求他从其描述中脱颖而出。

支持一种对象。Self提供了一个框架,该框架可以轻松地包含具有其自身行为的一种对象。由于每个对象都有命名的插槽,并且插槽可以保存状态或行为,因此任何对象都可以具有唯一的插槽或行为。基于类的系统旨在用于许多对象具有相同行为的情况。没有语言支持对象具有其自身的独特行为,并且创建一个保证只有一个实例的类很尴尬(认为单例模式)。自我不受这些缺点的影响。可以使用自己的行为自定义任何对象。唯一的对象可以拥有唯一的行为,并且不需要单独的“实例”。

消除元回归。在基于类的系统中,没有任何对象可以自给自足。需要另一个对象(其类)来表达其结构和行为。这导致了概念上无限的元回归:a point是class Point的实例,是metaclass的实例Point,后者是metametaclass的实例,是 Point无限的。另一方面,在基于原型的系统中,对象可以包含自己的行为;无需其他物体即可将生命呼吸。原型消除了元回归。

Self可能是实现原型的第一种语言(它还开创了其他有趣的技术,如JIT,后来又引入了JVM),因此阅读其他Self论文也应该具有启发性。


5
RE:消除元回归:在基于类的Common Lisp对象系统中,a point是class Point的实例standard-class,这是metaclass的实例,后者是其自身的实例ad finitum。
Max Nanasy 2012年

与自我论文的链接已失效。工作链接:自我:简单性的力量 | 自我书目
user1201917

24

你应该看看一个JavaScript的伟大的书道格拉斯·克罗克福德。它为JavaScript创建者做出的一些设计决策提供了很好的解释。

JavaScript的重要设计方面之一是其原型继承系统。对象是JavaScript中的一等公民,以至于常规函数也被实现为对象(准确地说是“函数”对象)。在我看来,当最初将其设计为在浏览器中运行时,它打算用于创建许多单例对象。在浏览器DOM中,您可以找到该窗口,文档等所有单例对象。而且,JavaScript是一种松散类型的动态语言(与之相对的Python是强类型的动态语言),因此,对象扩展的概念是通过使用'prototype'属性实现的。

因此,我认为以JavaScript实现的基于原型的OO有一些优点:

  1. 适用于松散类型的环境,无需定义显式类型。
  2. 使实现单例模式变得异常容易(在这方面比较JavaScript和Java,您会知道我在说什么)。
  3. 提供在不同对象的上下文中应用对象的方法,从对象动态添加和替换方法等的方式(在强类型语言中是不可能的)。

这是原型OO的一些弊端:

  1. 没有简单的方法来实现私有变量。可以使用Crockford的向导使用闭包来实现私有变量,但是绝对不像说Java或C#那样使用私有变量那么简单。
  2. 我还不知道如何在JavaScript中实现多重继承(其价值)。

2
只需为私有变量使用命名约定,就像Python一样。
aehlke 2010年

1
在js中,执行私有var的方法是使用闭包,这与您选择的继承类型无关。
Benja '10

6
Crockford在破坏JavaScript方面做了大量工作,因为一种相当简单的脚本语言已经演变成对其内部的精妙的迷恋。JS没有真正的私有关键字范围或真正的多重继承:请勿尝试伪造它们。
Hal50000
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.