基于原型与基于类的继承
Posted
技术标签:
【中文标题】基于原型与基于类的继承【英文标题】:prototype based vs. class based inheritance 【发布时间】:2010-10-23 09:32:50 【问题描述】:在 javascript 中,每个对象同时是一个实例和一个类。要进行继承,您可以使用任何对象实例作为原型。
在 Python、C++ 等中,有类和实例作为单独的概念。为了进行继承,您必须使用基类创建一个新类,然后可以使用该类生成派生实例。
为什么 JavaScript 会朝着这个方向发展(基于原型的面向对象)?与传统的、基于类的 OO 相比,基于原型的 OO 有哪些优点(和缺点)?
【问题讨论】:
JavaScript 受到 Self 的影响,Self 是第一个具有原型继承的语言。当时经典继承风靡一时,最早是在 Simula 中引入的。然而经典继承太复杂了。然后David Ungar和Randall Smith在阅读了GEB之后有了一个顿悟——“最具体的事件可以作为一类事件的一般例子。”他们意识到面向对象编程不需要类。于是自我诞生了。要了解原型继承如何优于经典继承,请阅读以下内容:***.com/a/16872315/783743 =) @AaditMShahGEB
是什么/谁?
@Alex GEB 是 Douglas Hofstadter 写的一本书。这是哥德尔·埃舍尔·巴赫的缩写。库尔特·哥德尔是一位数学家。埃舍尔是一位艺术家。巴赫是一位钢琴家。
【参考方案1】:
这里有大约一百个术语问题,主要是围绕某人(不是你)试图让他们的想法听起来像最好的。
所有面向对象的语言都需要能够处理几个概念:
-
数据的封装以及对数据的相关操作,这些操作被称为数据成员和成员函数,或数据和方法等。
继承,可以说这些对象与其他对象集一样,除了这些更改之外
多态性(“多种形状”),其中对象自行决定要运行哪些方法,以便您可以依靠语言正确路由您的请求。
现在,就比较而言:
首先是整个“类”与“原型”的问题。这个想法最初始于 Simula,其中使用基于类的方法,每个类表示一组共享相同状态空间(读取“可能值”)和相同操作的对象,从而形成一个等价类。如果你回顾一下 Smalltalk,因为你可以打开一个类并添加方法,这实际上与你在 Javascript 中可以做的一样。
后来的 OO 语言希望能够使用静态类型检查,因此我们得到了在编译时设置固定类的概念。在公开课版本中,您有更多的灵活性;在较新的版本中,您可以在编译器中检查某些需要测试的正确性。
在“基于类”的语言中,复制发生在编译时。在原型语言中,操作存储在原型数据结构中,在运行时被复制和修改。然而,抽象地说,一个类仍然是共享相同状态空间和方法的所有对象的等价类。当您向原型添加方法时,您实际上是在创建一个新的等价类的元素。
现在,为什么要这样做?主要是因为它在运行时提供了一种简单、合乎逻辑、优雅的机制。现在,要创建一个新对象,或创建一个新类,您只需执行深度复制,复制所有数据和原型数据结构。然后,您或多或少地免费获得继承和多态性:方法查找总是包括通过名称向字典询问方法实现。
最终出现在 Javascript/ECMA 脚本中的原因基本上是,当我们在 10 年前开始使用此脚本时,我们处理的计算机功能要弱得多,浏览器也不那么复杂。选择基于原型的方法意味着解释器可以非常简单,同时保留面向对象的理想属性。
【讨论】:
对,这个段落是不是好像我的意思不是一样? Dahl 和 Nyqvist 提出了“类”作为具有相同方法签名的事物的集合。 这种变化会更好吗? 不,抱歉,CLOS 来自 80 年代后期 dreamsongs.com/CLOS.html Smalltalk 从 1980 年 en.wikipedia.org/wiki/Smalltalk 和 Simula 从 1967 年到 68 年完全面向对象 en.wikipedia.org/wiki/Simula @Stephano,它们并没有那么明显:Python、Ruby、Smalltalk 使用字典进行方法查找,而 javascript 和 Self 有类。在某种程度上,您可能会争辩说,区别只是面向原型的语言公开了它们的实现。所以最好不要把它变成大交易:它可能更像是 EMACS 和 vi 之间的争论。 有用的答案。 +1 cmets 中不太有用的垃圾。我的意思是首先是 CLOS 还是 Smalltalk 有区别吗?无论如何,这里的大多数人都不是历史学家。【参考方案2】:您应该通过Douglas Crockford 查看great book on JavaScript。它很好地解释了 JavaScript 创建者做出的一些设计决策。
JavaScript 的重要设计方面之一是其原型继承系统。对象是 JavaScript 中的一等公民,以至于常规函数也被实现为对象(准确地说是“函数”对象)。在我看来,当它最初设计为在浏览器中运行时,它是用来创建大量单例对象的。在浏览器 DOM 中,您会发现窗口、文档等都是单例对象。此外,JavaScript 是松散类型的动态语言(与 Python 是强类型的动态语言相反),因此,通过使用 'prototype' 属性实现了对象扩展的概念。
所以我认为在 JavaScript 中实现的基于原型的 OO 有一些优点:
-
适用于松散类型的环境,无需定义显式类型。
实现单例模式非常容易(在这方面比较 JavaScript 和 Java,你就会知道我在说什么)。
提供在不同对象的上下文中应用对象的方法、从对象动态添加和替换方法等的方法(这在强类型语言中是不可能的)。
以下是原型 OO 的一些缺点:
-
没有简单的方法来实现私有变量。使用Crockford 的魔法使用closures 实现私有变量是可能的,但它绝对不像在Java 或C# 中使用私有变量那么简单。
我还不知道如何在 JavaScript 中实现多重继承(就其价值而言)。
【讨论】:
只需对私有变量使用命名约定,就像 Python 一样。 在 js 中实现私有变量的方式是使用闭包,这与您选择的继承类型无关。 Crockford 在破坏 JavaScript 方面做了很多工作,因为一个相当简单的脚本语言已经变成了对其内部结构的大师级迷恋。 JS 没有真正的私有关键字作用域或真正的多重继承:不要试图伪造它们。【参考方案3】:可以在论文Self: The Power of Simplicity 中找到稍微偏向于基于原型的方法的比较。该论文提出以下支持原型的论点:
复制创造。从原型创建新对象是通过一个简单的操作完成的,复制,一个简单的生物 比喻,克隆。从类创建新对象完成 通过实例化,其中包括对格式的解释 课堂上的信息。实例化类似于盖房子 从一个计划。复制对我们来说是一个更简单的隐喻,而不是 实例化。
已有模块示例。原型比类更具体,因为它们是对象的示例而不是描述 格式和初始化。这些例子可以帮助用户重用 模块,使它们更易于理解。基于原型的系统 允许用户检查一个典型的代表,而不是 要求他从描述中理解。
支持独一无二的对象。 Self 提供了一个框架,可以轻松地将独一无二的对象包含在它们自己的行为中。 由于每个对象都有命名槽,槽可以保存状态或 行为,任何对象都可以有唯一的槽或行为。基于类 系统是为有许多对象的情况而设计的 相同的行为。一个对象没有语言支持 拥有自己独特的行为,创建一个保证只有一个的类很尴尬 实例 [想想单身人士 模式]。 Self 没有这些缺点。任何物体 可以根据自己的行为进行定制。一个独特的对象可以容纳 独特的行为,不需要单独的“实例”。
消除元回归。基于类的系统中的任何对象都不能自给自足;需要另一个对象(它的类)来表达 它的结构和行为。这导致了概念上的无限 元回归:
point
是类Point
的一个实例,它是一个 元类实例Point
,它是元元类的一个实例Point
,无限。另一方面,在基于原型的系统中 一个对象可以包含它自己的行为;不需要其他对象 为它注入活力。原型消除了元回归。
Self 可能是第一种实现原型的语言(它还开创了其他有趣的技术,如 JIT,后来进入 JVM),因此阅读the other Self papers 也应该是有益的。
【讨论】:
RE:消除元回归:在基于类的 Common Lisp 对象系统中,point
是类Point
的一个实例,它是元类@987654332 的一个实例@,它是它自己的一个实例,无穷无尽。
自我论文的链接已失效。工作链接:Self: The Power of Simplicity | A Self Bibliography以上是关于基于原型与基于类的继承的主要内容,如果未能解决你的问题,请参考以下文章