过程式编程与面向对象编程相比,是否有任何优势?

41

[编辑:] 我之前提出了一个也许表述不当的问题,询问何时使用面向对象编程(OOP)和过程式编程(procedural programming),有一些回答暗示我需要帮助理解 OOP。相反,我已经广泛使用了 OOP,但想知道何时使用过程化方法。根据回复,我认为普遍存在这样的共识:OOP通常是更好的全面方法,但如果 OOP 架构在长期内无法提供任何重用优势,则应使用过程语言。

然而,我的 Java 程序员经验与此不同。我看到了一份由我设计的庞大 Java 程序被 Perl 大师以十分之一的代码重写,并且看似与我的 OOP 完美模型一样健壮。我的架构实现了大量的重用,但更简洁的过程化方法产生了更优秀的解决方案。

因此,冒着重复自己的风险,我想知道在什么情况下应选择过程化而不是面向对象的方法。您如何预先确定 OOP 架构可能过于臃肿而过程化方法更简洁高效的情况。

有人能提供这些情景的例子吗?

如何预先确定一个项目最好采用过程式编程方法?


2
这个问题让我困扰,因为我想说OOP(面向对象编程)...但如果你如此不确定以至于要问这个问题,并且你采纳了OOP的建议,你可能会写出一些假装是OOP的过程式代码。 - theman_on_vista
@theman 说得很对。面向对象编程几乎适用于任何情况,但如果你不理解它,你只会带来更多的伤害而不是好处。 - Ty.
1
你说Perl程序似乎也很强大。关于语言的争论我不发表任何意见,但我认为普通程序员无法维护Perl代码,项目应该由专家编写,以便普通程序员能够维护它们。 - aaaa bbbb
他的解决方案是严格按照过程进行的吗?你可以在Perl 5.8中编写面向对象的代码。 - Richard H
这让我想知道他为什么要重写它。很多非面向对象的开发人员使用“如果它没有出问题,就不要修复它”来反对重构成面向对象编程。但在这种情况下,这个格言被抛到了一边... - toddmo
23个回答

44

我喜欢Glass'关于重用的三个规则(这似乎是您感兴趣的内容)。

1)构建可重用组件比构建单次使用组件难3倍。
2)一个可重用组件应在三个不同的应用程序中尝试使用,然后才能被视为足够通用以接受到重用库中。

从这个中可以推断出以下推论:

a)如果您没有预算,使您的时间增加3倍,那么也许您应该暂停重用。(假设难度=时间)
b)如果您没有3个将使用正在构建的组件的位置,也许您应该暂停构建可重用组件。

我仍然认为OOP对于构建单次使用组件很有用,因为您总可以将其重构为真正可重用的内容。 (您也可以从PP重构为OOP,但我认为OOP具有足够的组织和封装优势,可以从那里开始)


1
看起来是一本有趣的书。 - RichardOD
将几个单一使用的组件更改为标准化的可重用组件需要增加多少难度? - ahnbizcad
过程化编程并不总是等同于单次使用,而面向对象编程也不总是等同于可重用。你可以将过程化代码放入对象方法或类方法中,并使其成为单次使用。然后,在使用3次左右后,当你准备将其变为多次使用时,“包装”已经完成,你只需拆分方法,将变量替换为实例变量等即可。这就像在你准备从一垒跑到二垒时,有几步领先的起点...也许你会,也许你不会,但最好还是走出几步。 - ahnbizcad

19

可重用性(或缺乏可重用性)不仅限于特定的编程范式。根据需要使用面向对象,过程化,函数式或任何其他编程方式。组织和可重用性取决于你所做的事情,而不是工具本身。


5
确实:这是Unix哲学背后的主要概念:小巧、设计良好的程序相互协作。 - Christopher Mahan

19

那些宗教般支持面向对象编程(OOP)的人没有任何事实来证明他们的支持,正如我们在这些评论中看到的一样。他们受过大学的培训(或洗脑),只使用和赞扬OOP,这就是为什么他们会如此盲目地支持它。除了在团队环境中保护代码免受粗心程序员的影响外,OOP并没有太多提供。个人经验表明,我在PP和OOP方面工作多年,发现PP更加简单、直接和高效,并且我同意以下智者的观点:

(参考http://en.wikipedia.org/wiki/Object-oriented_programming ):

许多知名的研究人员和程序员批评了OOP。以下是一个不完整的列表:

  • Luca Cardelli撰写了一篇题为“Bad Engineering Properties of Object-Oriented Languages” 的论文。

  • Richard Stallman在1995年写道,“将OOP添加到Emacs中不是明显的改进;当我在Lisp机器窗口系统上工作时,我使用了OOP,我不同意通常认为这是一种更优秀的编程方式的观点。”

  • Potok等人的一项研究显示,在OOP和过程化方法之间没有显著的生产力差异。

  • Christopher J. Date表示,由于缺乏关于OOP的一个经过协商的严谨的定义,因此很难将OOP与其他技术(特别是关系型)进行批判性比较。提出了一个关于OOP的理论基础,将OOP用作一种可定制的类型系统,以支持RDBMS。

  • Alexander Stepanov建议,OOP提供了一种数学上有限的观点,并称其为“几乎和人工智能一样具有欺骗性”(可能是指1980年代的人工智能项目和营销,现在有些看法已被认为过于狂热)。

  • Paul Graham提出,面向对象编程的目的是作为一种“驱群机制”,使得平庸程序员在平庸的组织中“造成的破坏不至于太大”,这是以减缓知道如何使用更强大和更紧凑技术的高效程序员为代价的。

  • Erlang的主要发明人Joe Armstrong曾说过:“面向对象语言的问题在于它们带着所有这些隐含的环境。你想要的是香蕉,但你得到的却是握着香蕉的大猩猩和整个丛林。”

  • 作者、前《COMPUTE!》杂志编辑Richard Mansfield认为:“像多年来的无数智识时尚(‘相关性’、共产主义、‘现代主义’等等——历史上充斥了这些),面向对象编程将与我们同在,直到现实最终显现出来。但考虑到面向对象编程目前渗透了大学和工作场所,它可能会被证明是一种持久的错觉。整整一代接受过洗脑的程序员仍然离开学院,致力于面向对象编程,而且只有面向对象编程,其余的都不要。” 他也说过:“面向对象编程对于写程序来说,就像通过机场安检对于飞行一样”。


  • 1
    我使用过面向对象编程和非面向对象编程语言和方法编写了许多项目。我认为面向对象编程的最大问题源于Java及其衍生语言未能区分值、非共享值持有者和实体之间的差异。当处理实体时,面向对象系统比过程式系统更好;但并不是所有东西都是实体,而且对实体的强烈设计偏见可能会使它们在处理值和非共享值持有者时效率较低。 - supercat
    我认为与OOP相关的最大问题是很少有人真正知道如何使用它。你不能只是把过程式代码塞进类中。看起来OP对OOP有偏见,因为他实际上无法正确地使用它。说在大多数情况下与过程式相比,OOP更糟糕,这是相当可笑的。 - Jimbo
    1
    优秀的回答。我个人认为,面向对象编程是编程历史上最昂贵的错误之一。虽然我大部分职业生涯都在使用它,但是看到了函数式编程(甚至在Java中),如果由我决定,我会对面向对象编程置之不理。就像你所说的,它帮助平庸的程序员更多地考虑组织,但代价是让100倍开发者从更优雅和正确的角度做事情变得缓慢。大多数程序员并不真正知道何时正确地组合和分解对象。 - Sridhar Sarnobat
    完全同意。最终,计算机是由处理器和内存构建的。如果您加载对象并编写更长的代码,则成本更高。面向对象编程需要消耗资源且混乱。在编译需要数天并且必须回收已编译对象的情况下,面向对象编程是有意义的,考虑到您可以节省时间但会失去存储空间。但是,在今天,特别是使用解释型语言(如php)时,这种编程方式已经没有意义了。 - PalDev

    14

    你已经给出了答案——大型项目需要OOP来避免过于混乱。

    在我的观点中,面向对象编程的最大优势是代码组织。这包括DRY原则和封装性。


    2
    DRY代表什么?我在谷歌上搜索了一下,但没有找到有用的信息。 - David Anderson
    9
    这并不是面向对象编程特有的。任何具有模块和数据隐藏的语言都是如此,包括C语言,尽管它与面向对象编程毫不相关。 - finnw
    1
    finnw,是的,但面向对象编程将这些思想提升到了更高的层次。 - mafu
    我同意面向对象编程几乎迫使程序员更加合理地组织他们的代码。但我必须说明一下,甲骨文数据库内核(数百万行代码)是用C语言编写的。 - Sridhar Sarnobat
    3
    我看过的大多数OO应用程序,它们的代码看起来像是成千上万个文件的混乱组合... WordPress、Matomo、Own Cloud、Roundcube、Drupal等等...似乎OO正在使用文件系统结构,这会造成很大的混乱。 - PalDev

    13

    我建议尽可能使用最简洁、基于标准的方法来解决任何问题。你的同事使用Perl表明了一位懂得某种特定工具的好开发者可以无论采用哪种方法都能取得出色的结果。我希望看到的不是将Java与Perl项目作为过程化与面向对象编程的好例子进行对比,而是想看到Perl和另一种同样简洁的语言比如Ruby之间的对决,因为Ruby也有面向对象编程的优势。这才是我真正想看到的。我的猜测是Ruby会获胜,但我不想在此引发语言战争 - 我只是想说您应该选择适合任务的工具 - 无论采用哪种方法都能以最高效且稳健的方式完成任务。Java可能因其面向对象而稳健,但像你和你的同事还有许多转向动态语言如Ruby和Python的人们正在发现,有更高效的解决方案,无论是过程式编程还是面向对象编程。


    7
    我认为DRY原则(不要重复自己)结合一点敏捷开发是一个好方法。从最简单的可行方案开始逐步构建程序,然后逐个添加功能,并根据需要随时进行代码重构。
    如果你发现自己一遍又一遍地写同样几行代码 - 可能使用不同的数据 - 现在应该考虑使用抽象来帮助区分变化的内容和不变的内容。
    为每个迭代创建彻底的单元测试,以便可以放心进行重构。
    花太多时间尝试预测哪些代码部分需要可重用是错误的。一旦系统开始增长,这一点很快就会变得显而易见。
    对于具有多个并发开发团队的大型项目,您需要拥有某种架构计划来指导开发,但如果您是独自工作或在小型合作团队中工作,则遵循DRY原则将自然形成架构。
    这种方法的另一个优点是无论你做什么都基于真实世界的经验。我最喜欢的类比 - 在想象如何构建建筑之前,你必须先玩乐高积木。

    6
    我认为当你有一个非常明确的问题,规范不会改变,并且想要一个运行速度非常快的程序时,应该使用过程式风格。在这种情况下,你可能会为了性能而牺牲可维护性。
    通常情况下,当你编写游戏引擎或科学模拟程序时,就是这种情况。如果你的程序每秒计算超过一百万次以上,那么它应该被优化到极致。
    你可以使用非常高效的算法,但是除非你优化缓存使用,否则它不够快。如果你的数据被缓存,这可能会带来很大的性能提升。这意味着CPU不需要从RAM中获取字节,因为它知道它们在哪里。为了实现这一点,你应该尽量将数据存储在彼此靠近的位置上,你的可执行文件和数据大小应该尽量小,并尽量使用较少的指针(在你能负担得起的地方使用静态全局固定大小的数组)。
    如果你使用指针,你会不断地在内存中跳跃,你的CPU需要每次重新加载缓存。面向对象的代码充满了指针:每个对象都按其内存地址存储。你随处调用`new`,这会将你的对象散布在内存中,使得缓存优化几乎不可能(除非你有一个分配器或垃圾收集器,可以使事物彼此靠近)。你调用回调和虚函数。编译器通常无法内联虚函数,而虚函数调用相对较慢(跳转到VMT,获取虚函数地址,调用它[这涉及将参数和本地变量推入堆栈,执行函数然后弹出所有内容])。当你每秒运行25次从0到1000000的循环时,这一点非常重要。通过使用过程式风格,没有虚函数,优化器可以在这些热循环中内联所有内容。

    5
    我认为OOP的适用性更多地取决于您所从事的主题领域,而不是项目的大小。 有一些主题领域(CAD,仿真建模等)其中OOP自然映射到相关概念。 然而,在许多其他领域中,映射最终变得笨拙和不协调。 许多人在所有情况下都使用OOP似乎花费了很多时间试图将方形钉子敲入圆孔中。
    OOP有其位置,但过程式编程,函数式编程等也同样重要。 查看您要解决的问题,然后选择允许您编写最简单可能的程序来解决它的编程范例。

    1
    如果您不介意的话,我想知道您是否可以编辑您的回复,给出一个面向对象编程可能不合适的情况的例子?我非常想听听您对此的论点。 - Patrick O Connell

    5
    如果项目非常小,可以在一个类中完成,并且不会长时间使用,我建议使用函数。另外,如果您使用的语言不支持面向对象(例如C语言),也可以使用函数来实现。

    5
    “面向对象编程语言的问题在于它们带有许多隐含的环境。你想要一只香蕉,但你得到的是一只拿着香蕉的大猩猩和整个丛林。” —Joe Armstrong
    你想要整个丛林吗?

    1
    如果你正在建造一个丛林,没错。 如果你只想要香蕉,那就算了。你有多频繁地只需要香蕉呢?隐式环境的问题不仅限于面向对象编程。过程式编程也存在这个问题,而且更糟糕,因为它没有封装。改变一件事情,你不知道潜在的影响会有多远及广。 - ahnbizcad

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