经典继承与原型继承

21

阅读了两者的介绍后,我很好奇编程社区如何使用它们?在什么情况下会使用哪一个?

3个回答

29

传统继承存在许多问题,而原型继承则不存在这些问题,例如:

传统继承

紧密耦合。继承是面向对象设计中最紧密的耦合方式。派生类对其祖先类有深入的了解。

不灵活的层次结构(也称必要的重复)。单个父类层次结构很少能描述所有可能的用例。最终,所有层次结构都会对新的用例“不太适用”--这是一个需要代码重复的问题。

多继承很复杂。继承多于一个父类通常是可取的。这个过程非常复杂,并且其实现与单一继承的过程不一致,使得阅读和理解更加困难。

脆弱的架构。由于紧密耦合,经常很难重构具有“错误”设计的类,因为许多现有功能依赖于现有设计。

大猩猩/香蕉问题。通常有部分父类属性你不希望继承。子类允许你覆盖来自父类的属性,但它不允许你选择要继承哪些属性。

原型继承

为了理解原型继承如何解决这些问题,首先应该了解两种不同类型的原型继承。JavaScript支持两种:

委托。如果在实例上找不到一个属性,将在其原型上查找该属性。这使您可以在多个实例之间共享方法,为您提供轻量级模式​​

串联。动态添加属性的能力使您可以自由地将任何属性从一个对象复制到另一个对象中,全部或部分地复制。

您可以同时结合这两种形式的原型继承,以实现非常灵活的代码重用系统。 事实上,使用原型很容易实现传统继承。反之不成立。

原型继承允许实现大多数经典语言中的重要特性。在JavaScript中,闭包和工厂函数允许您实现私有状态,而函数式继承可以轻松地与原型相结合,以添加支持数据隐私的混合内容。
一些原型继承的优点:
1. 松散耦合。实例不需要直接引用父类或原型。虽然可以存储对象原型的引用,但不建议这样做,因为那会导致对象层次结构中最大的陷阱之一-紧密耦合。
2. 扁平化层次结构。使用串联和委托,可以在原型面向对象中轻松保持继承层次结构扁平 - 拥有单个委托的单个实例,并且没有对父类的引用。
3. 简单的多重继承。从多个祖先继承就像使用串联从多个原型中组合属性以形成新对象或新代理一样容易。
4. 灵活的体系结构。由于可以有选择地继承原型面向对象,因此无需担心“错误设计”问题。新类可以从任意数量的源对象的任意组合中继承任何组合的属性。由于层次结构扁平化的容易性,一个地方的更改不一定会在长链的后代对象中引起连锁反应。
5. 消除了大猩猩香蕉问题。选择性继承消除了大猩猩香蕉问题。
我不知道任何经典继承对原型继承的优势。如果有人知道,请告诉我。

1
经典继承的优势不是它比原型继承更快吗,特别是在使用编译时静态类(如C++)的情况下?或者你的意思是严格限制讨论只涉及JavaScript? - Todd Lehman
如何在JavaScript中进行有选择地继承? - Lakshay Dulani
1
@Lakshay 使用串联继承。在这里介绍:JavaScript中关于继承的常见误解 - Eric Elliott
虽然有些偏见,但你的最后一条评论证明你还没有对经典优势进行深入思考。我是JS开发人员,但我能看到静态分析、更强大的IDE重构功能、即时文档化等带来的好处,总结来说,这些优势让我更快地深入研究一些外部代码,尤其是当它的所有者不信奉你所信奉的原则时。 - floribon
我特别谈论JavaScript(由OP标记),它没有静态类型。在JavaScript中的类与Java中的静态类完全不同。instanceof是一种不可靠的检查方式,当对象被扩展或跨执行上下文边界时无法正常工作。即使在TypeScript中,接口也是更可靠的开发工具机制。 - Eric Elliott
显示剩余2条评论

9
原型继承更加灵活。任何现有的对象都可以成为一个类,从而产生额外的对象。这对于您的对象提供多个服务和/或它们在程序到达需要继承的点之前经历了许多状态转换时非常方便。
有关建模方法的广谱讨论,请参见此处:http://steve-yegge.blogspot.com/2008/10/universal-design-pattern.html

4

因为JavaScript不支持大多数人理解的“经典”继承(并且您没有提供任何阅读参考资料),我假设您指的是像这样处理继承:-

 function base() {
    var myVar;
    this.someBaseFunc = function() { }
 }


 function derived() {
    base.call(this);
    var someOtherVar;
    this.SomeOtherFunc = function() { }
 }

我通常的经验法则是:

  • 如果类很复杂,实例很少,请使用“经典”方法。这样可以使类仅公开应该公开的功能。
  • 如果类很简单,实例很多,请使用原型方法。这将限制重复定义和存储函数引用的开销,因为每次创建实例时都需要这样做。

3
很抱歉,我不喜欢给答案点踩,但你的回答都不正确。
  • 要实现封装,不需要使用经典继承,只需要闭包即可(可以通过工厂函数和原型轻松实现)。
  • 在JavaScript中使用经典继承仍然可以利用原型委托(通常也确实这么做)。经典继承只是指一个新实例从父类继承而来,形成父子层次结构,而不是像原型式继承那样从对象实例继承。
- Eric Elliott
哦,闭包(部分求值函数),或者利用周围范围内变量的函数可以像对象一样使用,具有多态和封装特性,比对象更轻量级,尽管你只能调用函数而对象通常有几个方法。 - aoeu256
所有在函数内定义的方法都可以通过闭包访问该函数的私有数据。这些数据被封装起来。const createSecret = secret => ({ getSecret: () => secret, setSecret: newSecret => createSecret(newSecret) }) - Eric Elliott

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接