与基于类的OOP相比,基于原型的OOP有何优势?


47

当我主要在基于类的语言的上下文中处理OOP之后第一次开始对Javascript进行编程时,我对为什么基于原型的OOP会比基于类的OOP更为困惑。

  1. 使用基于原型的OOP的结构优势是什么?(例如,在某些应用程序中,我们希望它更快或更省内存吗?)
  2. 从编码人员的角度来看,优点是什么?(例如,使用原型制作某些应用程序或扩展其他人的代码是否容易?)

请不要把这个问题看做是关于Javascript的问题(多年来存在很多与原型无关的错误)。相反,请结合原型与类的理论优势来研究它。

谢谢。


Answers:


46

在用Java编写RPG游戏时,我对这两种方法都有很多经验。最初,我使用基于类的OOP编写整个游戏,但最终意识到这是错误的方法(随着类层次结构的扩展,它变得难以维护)。因此,我将整个代码库转换为基于原型的代码。其结果是很多更好,更容易管理。

如果您有兴趣,请在此处获取源代码(Tyrant-Java Roguelike

主要好处如下:

  • 创建新的“类”很简单 -只需复制原型并更改几个属性即可,等等。我用它来定义新类型的药水,例如每行3-6行Java。比新的类文件和样板文件好得多!
  • 只需很少的代码就可以构建和维护大量的“类” -例如,Tyrant拥有大约3000种不同的原型,总共只有大约42,000行代码。对于Java来说,这真是太神奇了!
  • 多重继承很容易 -您只需从一个原型中复制属性的子集,然后将其粘贴到另一原型中的属性上即可。例如,在RPG中,您可能希望“钢魔像”具有“钢对象”的某些属性,“魔像”的某些属性以及“非智能怪物”的某些属性。使用原型很容易,请尝试通过继承层次结构来实现。
  • 您可以使用属性修饰符来做一些聪明的事情 -通过将巧妙的逻辑放入通用的“读取属性”方法中,可以实现各种修饰符。例如,定义一个魔术环可以很容易地为佩戴它的人增加+2的力量。这样做的逻辑是在环形对象中,而不是在“读取强度”方法中,因此您避免了在代码库中的其他位置进行大量的条件测试(例如,“字符是否戴着强度的环?”)
  • 实例可以成为其他实例的模板 -例如,如果您想“克隆”一个对象,这很容易,只需将现有对象用作新对象的原型即可。无需为不同的类编写很多复杂的克隆逻辑。
  • 在运行时更改行为非常容易 -即,您可以在运行时任意更改属性并“任意变形”对象。允许很酷的游戏内效果,如果将其与“脚本语言”结合使用,则在运行时几乎可以进行任何操作。
  • 它更适合于“功能性”的编程风格 -您倾向于发现自己编写了许多函数来适当地分析对象的行为,而不是在附加到特定类的方法中嵌入逻辑。我个人更喜欢这种FP风格。

主要缺点如下:

  • 您将失去静态类型的保证 -因为您正在有效地创建动态对象系统。这往往意味着您需要编写更多测试以确保行为正确,并且对象具有正确的“种类”
  • 这会带来一些性能开销 -由于通常会强制读取对象属性来进行一个或多个映射查找,因此就性能而言,您需要付出少量费用。就我而言,这不是问题,但是在某些情况下可能是一个问题(例如,在每个帧中都有很多对象被查询的3D FPS)
  • 重构的工作方式不同 -在基于原型的系统中,您实质上是在使用代码“构建”继承层次结构。IDE /重构工具无法真正帮助您,因为它们无法理解您的方法。我从来没有发现这个问题,但是如果您不小心,它可能会失控。您可能需要测试来检查您的继承层次结构是否正确构建!
  • 这有点陌生 -习惯了传统OOP风格的人们可能会很容易感到困惑。“你是什么意思,只有一个叫“事物”的课程?!?” -“我该如何扩展最后的Thing课程!?!” -“您违反了OOP原则!!!” -“让所有这些静态函数作用于任何类型的对象都是错误的!!!?”

最后是一些实现说明:

  • 我使用Java HashMap作为属性,并使用“父”指针作为原型。这种方法运行良好,但具有以下缺点:a)有时必须通过较长的父链来追溯属性读取,从而影响性能b)如果您更改了父原型,则更改将影响所有未覆盖更改属性的子级。如果您不小心,可能会导致细微的错误!
  • 如果再次执行此操作,则将对属性使用不变的持久性映射(类似于Clojure的持久性映射),或者使用我自己的Java持久性哈希映射实现。然后,您将获得廉价复制/更改以及不可变行为的好处,而无需将对象永久链接到其父级。
  • 如果将函数/方法嵌入到对象属性中,您将很有趣。我在Java中为此使用的hack(“ Script”类的匿名子类型)不是很优雅-如果再次执行此操作,则可能会对脚本(Clojure或Groovy)使用适当的易于嵌入的语言


(+1)很好的分析。Altought,它更基于Java模型,例如Delphi,C#,VB.Net具有显式属性。
umlcat 2011年

3
@umlcat-我认为Java模型与Delphi / C#模型几乎相同(除了用于属性访问的漂亮语法糖之外)-您仍然必须在类定义中静态声明所需的属性。原型模型的一点是,这个定义也不是一成不变的,你不作任何声明事先....
mikera

这错过了一个大的。您可以更改原型,从而有效地更改每个实例上的属性,即使在创建属性后也无需接触原型的构造函数。
Erik Reppen 2013年

关于多重继承要容易些,您将其与不支持多重继承的Java进行了比较,但是与支持它的语言(例如C ++)相比是否更容易?
Piovezan

2

基于原型的OOP的主要优点是可以在运行时扩展对象和“类”。

在基于类的OOP中,有几个不错的功能,不幸的是,它取决于编程语言。

对象Pascal(Delphi),VB.Net和C#具有非常直接的方式来使用属性(不要与字段混淆)和属性的访问方法,而Java和C ++则通过方法来访问属性。PHP混合了两者,称为“魔术方法”。

尽管主要的类OO语言具有静态类型,但仍有一些动态类型类。我认为使用OO类进行静态输入非常有用,因为它允许使用一种称为Object Introspection的功能,该功能可以使这些IDE可视化并快速地开发网页。


0

我必须同意@umlcat。类扩展是一个主要优势。例如,假设您想长时间为字符串类添加更多功能。在C ++中,您可以通过继续继承以前的字符串类生成来实现。这种方法的问题在于,每一代实际上都变成了自己的不同类型,这可能导致大量重写现有代码库。使用原型继承,您只需将新方法“附加”到原始基类...上,就不会在任何地方大规模构建继承类和继承关系。我希望看到C ++在其新标准中提出了类似的扩展机制。但是他们的委员会似乎由想要添加吸引人的功能和受欢迎功能的人管理。


1
您可能想要阅读Monoliths“ Unstrung”std::string该成员已经有太多成员,这些成员应该是独立算法,或者至少是非朋友非成员。而且无论如何,只要您可以修改原始类,就可以在不更改内存布局的情况下添加新的成员函数。
Deduplicator
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.