面向对象编程的优点


35

注意:这个问题是我几个月前写的博客文章的摘录。在对Programmers.SE 的评论中放置了指向博客的链接后,有人要求我在此处发布问题,以便他们回答。此公告是我最流行,因为人们似乎键入“我不明白面向对象编程”到谷歌很多。请随时在此处回答,或在Wordpress上发表评论。

什么是面向对象的编程?没有人给我满意的答案。我觉得您不会从一个鼻子朝天说“对象”和“面向对象”的人那里得到一个很好的定义。从一个只做面向对象编程的人那里也不会得到一个好的定义。没有人同时了解过程式程序设计和面向对象程序设计,我从未对一种面向对象程序的实际作用有一致的认识。

有人可以给我他们关于面向对象编程优势的想法吗?


2
噢,好人-提出一个定义,每个说自己做OOP的人都同意!(甚至忽略了那些实际上只是在做例如OOP服装中的过程编程的人。)
SamB 2010年

10
听起来确实像是“我没有C编程。我们不能只使用汇编语言吗?” 对我来说。
tia

2
@Joel:我不一定知道和您一样的无知支持者,但是我认为其中很大一部分可能是因为它们是由OO语言类引入编程的。如果这是您的基准,那么您将不知道它是如何提高的。我的第一语言是Applesoft BASIC,在被Delphi和C ++引入OOP之前,我已经学过一些BASIC方言,以及C,Pascal和一些x86汇编语言。经历过差异的人能够更好地解释它。
梅森惠勒

3
有关OOP的更多负面评论,请访问这里:有害.cat-v.org / software / OO_programming,包括Dijkstra和Rob Pike的引用。
imgx64 2010年

3
@乔尔:你打我反对OOP的头。面向对象的单向主义者通常对程序编程抱有稻草人的观点,对其他任何范例都几乎没有经验。函数式编程对他们来说似乎完全陌生(证据:出于某种奇怪的原因,最近有人问如何在Erlang中进行类和对象的人数),而我深信逻辑编程会激起他们的头脑。我只能想象一些实际上奇怪的范例会对他们产生什么影响。……
我的正确

Answers:


7

将软件视为计算机内部存在的机器或装配线。一些原材料和组件被送入机器,然后按照一系列程序将它们加工成某种最终产品。设置程序以特定顺序对某些原材料或组件执行特定操作,以达到一组特定的参数(例如,时间,温度,距离等)。如果要执行的操作细节不正确,或者机器的传感器未正确校准,或者某些原材料或组件不在预期的质量标准之内,则可能会改变操作的结果,并且产品不会合格如预期的那样。

这样的机器在操作和输入可接受方面非常严格。机器既不会质疑设计人员的智能,也不会质疑其当前的操作环境。只要有针对性,它将继续遵循程序。即使原材料或组件的变化可能对以后的操作产生重大影响,机器仍会执行其程序。需要对过程进行审查,以查看对程序进行哪些更改才能补偿并产生所需的结果。更改产品的设计或配置可能还需要对执行的操作或其顺序进行重大更改。尽管负责生产的人员很快了解到尽可能多地隔离操作以减少操作之间的不良影响的重要性,条件组件在进行处理时会做出很多假设;直到最终产品在某些不同的操作环境中由用户掌握之前,可能无法检测到的假设。

这就是过程编程的样子。

面向对象提供的是一种消除组件条件假设的方法。因此,要在该组件上执行的操作以及如何将其集成到最终产品中。换句话说,OOP就像获取处理某些特定组件的过程细节并将其交给较小的机器来执行一样。负责该过程的较大机器会告诉特定组件的机器它希望完成哪个操作,但是将步骤的详细信息留给特定组件的机器来处理。

关于面向对象相对于非面向对象软件的优势:

  • 特定组件的行为 -较小的特定于组件的机器负责详细说明如何处理特定组件,以确保在处理组件的任何时间,其机器都将适当地处理;
  • 多态表达式 -由于特定于组件的机器执行针对其特定组件的量身定制的操作,因此发送到不同机器的同一消息的行为可能有所不同;
  • 类型抽象 -通常,对于几种不同类型的组件而言,在其机器执行的操作中使用相同的词汇是有意义的;
  • 关注点分离 -将特定于组件的详细信息留给他们的机器意味着过程机器只需要处理其过程和管理过程所需的数据的更一般,更大的关注点;另外,它不太可能受到其他组件更改的影响;
  • 适应性强 -只需更改其使用的组件或将其提供给另一台处理机,即可将专注于其专业领域的组件调整为不可预见的使用;
  • 代码重用 -重点狭窄,适应性强的组件可以通过频繁使用来利用其开发成本。

3
您所说的一切似乎同样适用于函数式编程,从而突出了这里的问题。
杰西·米利坎

1
除了OP并未询问函数式编程之外,因此您的评论没有优点。特别是因为我的回答是被接受的。
2011年

函数式编程自然很接近面向对象,实际上有人可能会说函数式编程实际上是一种更“纯粹”的面向对象形式,其中函数本身成为第一类对象。但是,在所有函数式编程中,这仍然是菜鸟,对我而言,现在仍然是这样。
Newtopian

46

在您的博客中,您似乎既熟悉命令式编程也喜欢函数式编程,并且熟悉面向对象编程所涉及的基本概念,但是您从未真正对它进行“点击”使它有用。我将尝试根据这些知识进行解释,并希望它对您有所帮助。

从本质上讲,OOP是一种使用命令式范例通过创建对问题域建模的“智能”数据结构来更好地管理高度复杂性的方法。在(标准过程非对象程序)程序中,您有两个基本知识:变量和知道如何处理它们的代码。该代码从用户和其他各种来源获取输入,将其存储在变量中,对其进行操作,并生成输出数据,该数据将传递给用户或其他各种位置。

面向对象编程是一种采用基本模式并以较小的比例重复该程序来简化程序的方法。就像程序是一个包含大量数据的代码,这些代码知道如何处理程序一样,每个对象都是绑定到知道如何处理程序的代码的一小段数据。

通过将问题域分解为较小的部分,并确保将尽可能多的数据直接绑定到知道该如何处理的代码,您可以更轻松地对整个过程以及子过程进行推理。构成过程的问题。

通过将数据分组为对象类,您可以集中处理与该数据相关的代码,从而使相关代码更易于查找和调试。并且通过将数据封装在访问说明符后面,并仅通过方法(或属性,如果您的语言支持它们,则通过属性)对其进行访问,则可以大大降低数据损坏或违反不变性的可能性。

通过使用继承和多态性,您可以重用先前存在的类,对其进行自定义以满足您的特定需求,而不必修改原始类或从头开始重写所有内容。(如果可以避免的话,这绝对是不应该做事情。)请小心理解基本对象,否则可能会导致袋鼠杀手

对我而言,这些是面向对象编程的基本原理:通过创建对象类,继承和多态性来实现复杂性管理,代码集中化和改进的问题域建模,并通过使用封装和增强功能来提高安全性而不会牺牲功能或控制力属性。我希望这可以帮助您理解为什么这么多的程序员觉得它有用。

编辑:在评论中回应乔尔的问题,

您能解释一下“面向对象程序”包含的内容(除了您所概述的这些奇特的定义之外)与命令式程序根本不同的内容吗?您如何“使球滚动?”

这里有一点免责声明。我的“面向对象程序”模型基本上是Delphi模型,它与C#/。NET模型非常相似,因为它们是由前Delphi团队成员创建的。我在这里所说的话可能不适用于其他面向对象的语言,或者可能不太适用。

面向对象程序是其中所有逻辑都围绕对象构造的程序。当然,这必须在某个地方引导。典型的Delphi程序包含初始化代码,该代码创建一个名为的单例对象Application。在程序开始时,它将调用Application.Initialize,然后从头开始对Application.CreateForm要加载到内存中的每种表单都调用,然后Application.Run,在屏幕上显示主表单并启动输入/事件循环,该循环构成了任何形式的核心。交互式计算机程序。

应用程序和表单将轮询来自OS的传入事件,并将其转换为对对象的方法调用。很常见的一件事是使用事件处理程序,即.NET中的“代理”。一个对象的方法说:“执行X和Y,但还要检查是否已分配了此特定事件处理程序,如果已分配则调用它。” 事件处理程序是方法指针(一种非常简单的闭包,其中包含对该方法的引用和对对象实例的引用),用于扩展对象的行为。例如,如果我的表单上有一个按钮对象,我将通过附加一个OnClick事件处理程序来自定义其行为,该事件处理程序会导致其他对象在单击按钮时执行一个方法。

因此,在面向对象的程序中,大多数工作是通过定义具有一定职责的对象并将它们链接在一起来完成的,无论是通过方法指针还是通过一个对象直接调用在另一个对象的公共接口中定义的方法。(现在我们回到封装了。)这是一个想法,在我上大学的OOP课程之前,我没有任何回溯的想法。


4
我想您真的可以用这个答案打定主意。当您封装数据操作时(好的,他们在这里分配一个指针,然后在那儿移动几位...)剩下的就是程序的高级逻辑(如果做得正确,您可以在任何范式中编写可怕的代码)
ChaosPandion

2
很好的解释,谢谢。您能解释一下“面向对象程序”包含的内容(除了您所概述的这些奇特的定义之外)与命令式程序根本不同的内容吗?您如何“使球滚动?”
Joel J. Adamson

1
+1:我认为您真正通过“基本上是在较小规模上重复相同的事情”而获得了它,基本上是另一个抽象层次(或者,更像是另一个可能的层次来创建抽象)。
n1ckp 2010年

1
@Karthik派对晚了一点,但实际上没有,OOP不一定意味着只是重用课程。类型系统的整个概念是在公共接口下将对象分组在一起的抽象。但是,您也可以使用基于原型的系统(例如Javascript),在该系统上,对象上的方法调用可以解析原型链而不是类链。它仍然具有OOP的所有功能,但是通过将新的东西简单地添加到现有对象中就可以使用临时对象和类型。然后,您可以克隆该对象以获得更多的新类型。
CodexArcanum 2010年

2
+1巧妙地指出了OOP的真正优势。“从本质上讲,OOP是一种通过使用命令式范式通过创建对问题域建模的“智能”数据结构来更好地管理高度复杂性的方法。”
Karthik Sreenivasan 2012年

6

我认为OOP基本上只是一个名称,就像我过去一样,您可能一路上都想这样做。

早在我还是婴儿程序员的时候,即使在Fortran中,也有一个指向子例程的指针。能够将指向子例程的指针作为另一个子例程的参数传递是非常有用的。

然后,接下来真正有用的是将指向子例程的指针存储在数据结构的记录内。这样,您可能会说记录“知道”如何对其自身进行操作。

我不确定他们是否曾经将其内置到Fortran中,但是在C及其后代中很容易做到。

因此,在下面,这是一个简单而有用的想法,您可能会被诱惑去做自己的事,并且即使有人将它变成了一个充满令人恐惧的流行词的庞大潮流,但使用更新的语言更容易做到。


5

有各种各样的OO系统,很难获得每个人都会同意的定义。我不会尝试展示Java的OO与Common Lisp对象系统的相似之处,而是将逐步介绍一些更传统的方法。

假设您有许多作为分散数据存在的对象。例如,点可能是X,Y和Z数组中的元素。为了考虑一个点本身,将所有数据汇总到一个C中是有意义的struct

现在,对于任何数据对象,我们都将数据汇总在一起。但是,在程序程序中,代码是分散的。假设我们正在处理几何形状。有一个绘制形状的大功能,它需要了解所有形状。有一个很大的功能可以查找区域,另一个功能可以查找周边。圆的代码分散在多个函数中,为了添加另一种形状,我们需要知道要更改的函数。在面向对象的系统中,我们将函数收集到与数据相同的事物(class)中。因此,如果我们要查看所有的循环代码,则它在Circle定义中,如果要添加一个,Quartercircle我们只需编写其类,就可以得到代码。

这样做的一个好处是我们可以维护类不变式,这对于类的每个成员都是正确的。通过限制类外的代码避免直接与类数据成员混淆,我们获得了可以在一个地方更改类数据的所有代码,并且可以确认它不会做任何棘手的事情(例如,一个三角形带有一条腿)长于其他两个的总和)。这意味着我们可以依靠该类每个成员的某些属性,而不必每次使用时都检查对象是否健全。

主要好处来自继承和多态性。通过将所有这些各种形状定义为名为的类的子类Shape,我们可以使我们的代码操纵Shapes,并且形状子对象的工作就是执行操纵所要求的事情。这意味着当我们添加新形状或改进旧形状的行为时,我们无需触摸经过测试的旧代码。我们自动拥有可以直接利用新代码的旧代码。不必使控制代码知道所有可能的形状,而不必维护知道所有可能的形状的函数,我们只需处理形状及其属性,同时维护Shape子类。这简化了控制代码。

我们在这里有几个优势。由于我们具有类不变式,因此我们可以像处理内置类型一样来推理较大的数据对象,这意味着我们通常可以将复杂的概念分解为更简单的概念。由于圈子代码主要包含在中Circle,因此我们增加了位置。由于没有在不同位置的几个不同函数中散布圆的概念,因此例程之间的耦合较少,也不必担心保持同步。由于类实际上是类型,因此我们可以利用现有的类型系统来捕获对类的不兼容使用。


3

OO有许多不同的定义,是的。我相信您可以自己找到很多。我个人喜欢Rees Re:OO,以此来理解它们。我想自从您引用Paul Graham以来,您已经读过了。(我向对OO感兴趣的任何人推荐它。)我将在这里{1,2,3,7,8,9}或多或少地采用Java定义。

OO的实用性问题,特别是我的实现方式,应该用几千行代码来提供更大的答案(部分原因是不只是一堆断言)。但是,这是该假设文档的摘要。

我不认为OO在小规模(例如几百行)中非常有用。特别是,没有良好功能影响的OO语言往往使用任何种类的集合或需要许多数据类型的任何事情完成简单的事情变得非常痛苦。这是大多数设计模式发挥作用的地方。它们是底层语言功能低下的创可贴

在大约一千行处,要跟踪所有操作和数据结构以及它们之间的关系变得越来越困难。在这一点上,它有助于找到一种显式组织数据结构和操作,绘制模块边界和定义职责的方法,并在尝试针对它们进行编程时提供一种方便的方式来理解这些定义。

Java ish OO是恰好赢得了人气竞赛的这些问题的半途而废的解决方案。由于Java人将这种机制应用到由功能不足的语言所造成的小规模问题上,因此它倾向于开始看起来像是一种万事俱备的魔术解决方案,而不仅仅是保持井井有条的方式。熟悉函数式编程的人们往往更喜欢其他解决方案,例如CLOS或Haskell的类型类,或者在卡在C ++中时进行模板元编程,或者(例如我,每天在C#中工作)使用OO,但不要为此而感到兴奋。


+1-好答案。“我不认为OO在小范围内非常有用。”
Karthik Sreenivasan '02

我什至读过一篇文章(dl.acm.org/citation.cfm?id=326103),其中指出对于软件产品的前两个版本,OOP相对于程序上没有任何实际的生产力优势。据我了解,只有从第三个版本开始,您才具有真正的优势,因为与使用程序样式相比,您可以更好地重用/重构以OO样式编写的代码。
乔治

1

OOP尝试根据对象及其之间的交互对现实世界的概念进行建模。作为人类,我们倾向于按照物体来处理世界。这个世界充满了具有某些属性的对象,并且可以做某些事情,例如与其他对象进行交互。OOP允许以相似的术语建模世界。例如,

  • 人是对象。一个人具有一些属性,例如年龄和性别。一个人可以做的事情:吃饭,睡觉,开车。
  • 汽车也是一个对象(尽管类型不同)。它还具有诸如品牌,型号和年份的属性。汽车可以做的事情:移动。

但是汽车不能自行行驶,它需要一个人来驱动-对象之间的交互。


我明白了:这很有道理。但是,我认为对计算机进行编程的最酷的事情之一是,我们不必考虑“现实世界”对象如何执行操作。我从数学角度考虑(我是一位从事生物学研究的数学家)。这是一种“沉思”(沉思的见解),而不是一种态度。但是,它极大地影响了我的工作方式。
乔尔·亚当森

1

OOP =数据结构+消息传递+继承,所有这些都是编程模型中的逻辑演变。

(程序员)可以在大约90秒内了解OOP(有关链接,请参阅我的个人资料)。概念非常简单。

如何应用是另一回事。仅仅因为您知道如何挥动锤子并不意味着您知道如何设计和建造房屋。;-)


+1-我同意。如何应用OOP花费了大量的时间和精力,尽管仅仅知道它们是什么也没花很多时间,因为这这里的关键词
Karthik Sreenivasan'2


0

我最初的理解是:

在进行面向对象的编程之前,您需要进行结构化编程。一切都围绕该过程。您必须问自己的第一个问题是“ 我想对这些信息做什么? ”。

使用面向对象的编程,它以数据为中心。您必须问自己的第一个问题是“ 我需要处理的女巫信息? ”。这使抽象更加容易。


0

因为您了解结构,也了解函数指针,并且了解具有函数指针的结构,所以从您的角度来看,我将面向对象的编程定义为简单的“编程,大量使用具有函数指针的结构”。它仍然是传统意义上的编程-全部是数据,并且代码对数据起作用。区别仅在于如何定义所有这些信息以及您如何定义这些信息。

也许过于简化是传统编程是“带有某些数据结构的代码”,而面向对象的编程是“带有某些代码的数据结构”。两者仍然具有数据结构,并且仍然具有代码。因此,面向对象的编程无非就是预先定义数据类型,并为它们如何通过功能集进行通信而实施合同。

正如您所观察到的,存在大量的应用程序类别,对于实现解决方案而言,这并不是一个很好的方法。您似乎生活在一个主要由此类应用程序组成的世界中。在您的博客文章中,您提到了“ 99瓶啤酒”问题的实现(您的“最喜欢的编程展示”)。99瓶啤酒当然是该类别的一部分。通过查看99瓶啤酒的实现来尝试理解面向对象的编程,有点像通过查看树屋来理解高层建筑。即使是建造得很好的树屋也只能教给您很多东西。

TL; DR:OO编程与传统编程类似,不同之处在于您将更多精力放在预先定义数据结构上,并使这些数据结构通过函数指针相互通信。


-1

我认为Wikipedia页面是了解基础知识的好地方:http :
//en.wikipedia.org/wiki/Object-oriented_programming

基本上,想法是OOP试图改进的过程编程着重于建模过程。OOP转向一个模型,该模型的重点是您正在建模的“事物”,并且这些事物的过程和数据包含在这些事物中。

举个例子,假设您正在设计一个应用程序来跟踪任务列表。在过程编程中,模型中的顶级实体将是发生的过程,例如创建任务,删除任务,更改任务信息等。在OOP模型中,您将重点放在创建任务上,以及考虑一下Task应该负责哪些数据和流程。然后重点关注Task应与之交互的其他对象,例如便笺或一些您想保留有关Task的注释。

希望对您有所帮助。只要继续阅读它并查看代码,它就会突然“单击”。那是我的经验。


降级者照顾有何原因?
RationalGeek

看来您甚至可能都没有阅读过所提出的全部问题,更不用说戳入链接的博客条目了。据我所知,作者对基本面没有任何疑问。因此,基本上,-1是回答要回答的问题而不是要回答的问题。
Jesse Millikan
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.