面向对象编程(OOP) vs 函数式编程 vs 过程式编程

252

这些编程范式之间有什么区别,它们是否更适合特定问题,或者某些使用情况是否偏好其中任何一种?

欢迎提供架构示例!


这不是一个完整的答案,但我在这里写了一些关于“函数式”如何影响/对比“面向对象”风格(在F#的上下文中)的内容:http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!511.entry - Brian
你可能考虑阅读这个! 有许多例子可以说明何时使用哪种编程方式以及它们的主要区别、优缺点等。 - Nikita Ignatov
1
参见:https://dev59.com/vHI_5IYBdhLWcg3wFu_L - dreftymac
Uncle Bob已经在Twitter上发表了相关言论。此外,他还在这里发表了相关言论。 - jaco0646
7个回答

141

它们各有优点-它们只是解决同样问题的不同方法。

在纯过程式编程风格中,数据往往与操作它的函数高度解耦。

在面向对象编程风格中,数据往往伴随着一系列函数。

在函数式编程风格中,数据和函数倾向于彼此更相似(如Lisp和Scheme),同时在实际使用函数方面提供更大的灵活性。算法也往往是基于递归和组合定义的,而不是基于循环和迭代。

当然,编程语言本身只影响了哪种风格更受欢迎。即使在像Haskell这样的纯函数式编程语言中,您也可以采用过程式编程风格(尽管这是不被鼓励的),即使在像C这样的过程式编程语言中,您也可以采用面向对象编程风格(例如,在GTK +和EFL API中)。

需要明确的是,每种范式的“优势”仅在于算法和数据结构的建模。例如,如果您的算法涉及列表和树,函数式算法可能是最明智的选择。或者,如果您的数据高度结构化,则将其组合为对象可能更有意义,如果这是您的语言的本地范式-或者,它同样可以作为monad的函数式抽象来编写,这是Haskell或ML等语言的本地范式。

您使用哪种方法只是取决于您的项目和语言所支持的抽象。


6
你所说的似乎与你写的不一致。你说它们没有“优缺点”,但随后又说明它们是不同的方法。在任何给定的情况下,为什么有人会选择一种方法而非另一种方法?无论你称之为优劣或者优缺点,它们确实存在!我并不是说其中一种本质上更好,你也没有这么说。我相信这才是你真正想表达的。除非你真的认为任何选择的方法都没有相对于另一种方法的优缺点。 - J. M. Becker
1
@TechZilla:我同意我的措辞不够准确,但我的意思是,并没有一个功能列表可以指出语言X比Y更好,而不用说明语言X更适合编写算法U,而语言Y可能更适合编写算法V,尽管两种语言都可以轻松地实现。 - greyfade
2
"过程式编程和函数式编程的区别我不是很清楚。我正在大学学习Scheme/Rackett,但我真的看不出它与过程式C或PHP的主要区别,你能给一些例子吗?" - Leonel
7
大多数人会指出的最大区别是,在过程式语言中,你可能会使用for循环,而在函数式语言中,没有这样的东西——相反,你使用递归调用函数来执行相同的任务。函数式语言还将函数视为一等对象——你可以像处理数字一样传递它们——但是在C中你无法做到这一点(PHP对此的支持也有缺陷)。 - greyfade
2
@tastro:当一种范式比另一种更有意义时。这就是全部。有时将代码建模为函数的组合更有意义,有时将数据建模为对象更有意义。有许多表达算法和数据结构的方式。面向对象编程和函数式编程恰好是其中的两种。 - greyfade
显示剩余5条评论

25

我认为现在可用的库、工具、示例和社区完全超越了范式。例如,ML(或其他)可能是终极通用编程语言,但如果你不能获得任何适合你所做的事情的好库,那么你就会失败。

例如,如果你正在制作一个视频游戏,C++中有更多好的代码示例和SDK,所以你最好选择它。对于小型Web应用程序,有一些很棒的Python、PHP和Ruby框架可以让你快速上手。Java是大型项目的不错选择,因为它具有编译时检查和企业库和平台。

过去,不同语言的标准库相当小且容易复制 - C、C++、汇编语言、ML、LISP等都带有基础知识,但在标准化网络通信、加密、图形、数据文件格式(包括XML)甚至基本数据结构如平衡树和哈希表等方面,它们往往退缩!

现代编程语言如Python、PHP、Ruby和Java现在都配备了更好的标准库,并且有许多优秀的第三方库可以轻松使用,这在很大程度上要归功于它们采用命名空间来避免库之间的冲突,以及垃圾回收机制来规范库的内存管理方案。


5
Python、Ruby等语言不像C或LISP一样拥有“标准”库,因为它们是单个实现的语言。Python就是Guido说了算,没有标准可言。现在任何特定的C或LISP(或其他语言)实现都附带了一个大型的库集合,超出了标准库的范畴。 - Dan Andreatta
8
这个问题问的是面向对象、函数式和过程式编程之间的区别。虽然这些答案中提到的语言确实倾向于其中一种方法,但回答中没有提到任何这些概念...不管“可用的库[胜过]范例”与否,这并不能回答手头的问题,从而回避了一个完全合理的问题。 - rinogo
1
ircmaxell的相关抱怨:http://blog.ircmaxell.com/2012/07/oop-vs-procedural-code.html - rinogo

23

这些编程范式不一定是相互排斥的。以Python为例,它支持函数和类,但同时一切都是对象,包括函数。你可以在一段代码中混合使用函数式、面向对象或过程式风格。

我的意思是,在函数式语言中(至少在我学习的Haskell语言中),没有语句!函数只允许其中有一个表达式!!但是,函数是一等公民,可以像其他能力一样将它们作为参数传递。他们可以用少量的代码实现强大的事情。

而在过程式语言如C中,你只能通过使用函数指针来传递函数,但仅此一项并不能实现许多强大的任务。

在Python中,函数是一等公民,但它可以包含任意数量的语句。因此,你可以有一个包含过程式代码的函数,但你可以像函数式语言一样传递它。

面向对象也是如此。像Java这样的语言不允许你在类之外编写过程/函数。将函数传递的唯一方法是将其封装在实现该函数的对象中,然后将该对象传递。

但在Python中,你没有这个限制。


我相信你的意思是“这些范式不必互斥”。它们三个在理想情况下确实是正交的,如果你的编程语言允许的话,你可以在一个程序中使用其中一个、两个或三个。 - Joe Pineda
是的,我想“互斥”可能更准确一些!谢谢。 - hasen

16

对于GUI,我认为面向对象的范例非常适合。窗口是一个对象,文本框是对象,确定按钮也是一个对象。另一方面,像字符串处理这样的东西可以用简单的过程性范例完成,因此开销更小,更直接。

我不认为这是语言的问题。你几乎可以在任何流行的语言中编写函数式的、过程式的或面向对象的代码,虽然在某些语言中可能需要额外的努力。


18
有些想给你的回答点个踩,因为你误传了一个观念:“对象=GUI小部件”,但我决定克制自己。面向对象编程同样适用于表示抽象概念,比如“UserAccount”或“PendingSale”,就像对于可见界面元素如“Window”和“Button”一样。 - Dave Sherohman
6
我写了一个窗口可以是一个对象。你怎么从那里得出所有的对象都是窗口的结论呢?那只是一个例子。当然,面向对象编程同样适用于建模抽象实体及其关系。无论如何,谢谢你没有点踩。我没什么积分:D - panschk
6
面向对象编程(OOP)与GUI无关。最佳的GUI设计方法是使用外部文本文件(例如HTML)。而字符串处理之类的操作实际上更适合使用对象来完成(想想C语言中的字符串)! - hasen
1
我不知道,也许是因为我习惯了使用对象进行编程,所以没有意识到。但是如果不使用对象,你如何做一些交互式的事情,比如当 TextBox Y 的值被改变时,如何改变 TextBox X 中的值?好吧,你可以简单地为每个变量使用全局变量... - panschk
1
在Perl中,字符串处理做得非常好(比Java、C++或C#好100倍),但是该语言的字符串功能绝对不是面向对象的。C的字符串处理很糟糕,但是C并不是唯一的过程式语言(也不是最好的)。 - Joe Pineda
显示剩余4条评论

6
为了回答您的问题,我们需要两个要素:
1. 理解不同架构风格/模式的特点。 2. 理解不同编程范式的特点。
软件架构风格/模式列表显示在Wikipeida的software architecture article上。您可以轻松地在网上研究它们。
简而言之,过程化适用于遵循过程的模型,面向对象适用于设计,函数式适用于高级编程。
我认为您应该尝试阅读每个范式的历史,并看看人们为什么创建它,这样您就可以轻松理解它们。
在了解它们之后,您可以将架构风格/模式的项目链接到编程范式。

2

我认为它们通常不是“相对立”的,而是可以结合起来使用。我也认为,很多时候,你提到的这些词只是流行语。即使是最狂热的支持者,也很少有人真正知道“面向对象”是什么意思。


1

我的一个朋友正在使用NVIDIA CUDA编写图形应用程序。该应用程序非常适合面向对象编程范式,并且问题可以被很好地分解为模块。然而,要使用CUDA,您需要使用不支持继承的C语言。因此,您需要聪明一些。

a)您可以设计一个巧妙的系统,在某种程度上模拟继承。这是可以做到的!

i)您可以使用钩子系统,该系统期望每个父类P的子类C都具有对函数F的某种覆盖。您可以让子类注册其覆盖,这些覆盖将被存储并在需要时调用。

ii)您可以使用结构体内存对齐功能将子类转换为父类。

这可能很整洁,但是要想出一个具有未来性和可靠性的解决方案并不容易。你将花费大量时间设计系统,并且不能保证在项目进行到一半时不会遇到问题。如果要实现多重继承,甚至更难,几乎是不可能的。

b) 你可以使用一致的命名策略和分而治之的方法来创建程序。它不会有任何继承,但由于你的函数小巧、易于理解且格式一致,所以你不需要它。你需要编写的代码量会增加,很难保持专注,不陷入简单的解决方案(hack)中。然而,这种忍者式的编码方式是C语言的编码方式。在低级自由和编写良好代码之间保持平衡的好方法是使用函数式语言编写原型。例如,Haskell非常适合原型算法。

我倾向于b方法。我使用a方法编写了一个可能的解决方案,说实话,使用那段代码感觉非常不自然。


最初的C++编译器仅仅是生成C代码的预处理器。 因此,C语言可以实现C++的所有特性,包括多重继承。 (在C语言中模拟C++异常处理需要平台支持异常,但C++实现也需要,所以我认为这并不会使基本思想无效)。 - Chris Becke
2
@Chris Becke,你的回答太过哲学化。首先,C语言有多个标准(经过多年发展),其中很少有一个标准被C编译器完全采用,更不用说C++了。说C++是C的超集,因为它使用了C语法并没有什么意义,因为你甚至不能编写一种在另一个C编译器中编译的C编译器的代码而不需要大量的努力。此外,其他语言提供的语言特性(类型系统、OOD支持)在C中实现是不可能的,除非使用C来设计一种新语言(这正是为什么会有“新语言”的原因)。 - Sprague
你知道的,我已经看不出我的评论与这篇帖子有任何关联了。 :P - Chris Becke
Cuda现在已经支持C++了。 - Aryeh Leib Taurog

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