这将是一个非技术性的软性问题,我不确定这是否是正确的平台。但是我是CS的初学者,所以我希望你们能容忍它。 (Java是我的第一种编程语言)

按照我的理解,OOP是一种管理软件复杂性的方法。但是它的原理并不是新颖的或独特的,它们在某种意义上在所有工程领域都是通用的。良好定义的行为和接口。

但是我不明白引入新的编程范例的原因。我认为,用于管理复杂性的所有原理都可以通过过程编程语言来实现。例如,对于模块化,我们可以将程序分为许多小程序,这些小程序执行定义明确的任务,其代码包含在单独的文件中。这些程序将通过其定义明确的输入和输出相互交互。可以对文件进行保护(加密?)以实现封装。为了重新使用代码,我们只要在新程序中需要它们时就可以调用它们。这不是捕捉到所有的OOP还是我遗漏了非常明显的东西?我认为确实可以。但是我认为,用于处理复杂性的所有原理(例如模块化,封装,数据隐藏等)都可以很容易地由过程语言实现。那么,如果我们没有它就可以管理复杂性,那为什么是真正的面向对象呢?

评论

您缺少接口和实现的分离。能够在运行时将一个实现换成另一个实现是非常重要的功能。基本上,这是通过带有继承的OO语言的动态方法分派来实现的。过程语言也可以做到这一点(阅读:空指针),但是没有类型安全性。

很大程度上,面向对象的语言和设计的思想恰恰是使那些通用的,直观的概念尽可能容易地在代码中表示和重新创建。如果您有一个计划或一套准则来指导如何在没有固有的面向对象语言的情况下实现所有这些目标,那么关于如何做事的建议将有效地成为所使用的面向对象方法。实际的OO语言只是将其形式化和简化的一种方法。

@RobbieDee你真的读过我的问题吗?它是通过质疑天气可以在没有OO的情况下管理软件复杂性的一个更基本的层次上来理解OO。我不是要破坏OO,不是要发明新的东西,我只是想更好地理解它,如果问题如此“不言而喻”,为什么它得到了Jorg的好评?
加密文件不是封装。您可能已经看不清其他开发人员的代码内容,但不一定保护代码的内部工作不受其他代码的影响。如果原始作者可以回忆起如何做,可以在加密之前或之后进行。

除了机器语言,您不需要任何其他东西-让程序员记住操作码并自己编写一个零。但是,在减少错误和提高生产率方面,使用某种“符号”语言非常有用,而且,正如Dijkstra所观察到的那样,强加某种“结构”(或至少使“结构”的维护更加容易)的语言会大有帮助。考虑到当前的语言复杂程度,OO语言可能不是理想的技术,但对于许多应用程序来说它们还是不错的。这个想法是在不妨碍您的情况下管理复杂性。

#1 楼

让我尝试一个很低的理论答案:)

您真正要问的是:当可以使用过程语言来设计和编写OO时,为什么要直接在语言中包括对对象定向(OO)的支持?代码?

答案是:要有一个关于OO在源代码中如何表达的标准,这样您就不会以22种不同的实现实现同一抽象。

例如,假设我创建了一个可以在用户界面系统中使用的MagicButtonMagicSlider。我需要一种将可以与MagicButton一起使用的方法,只能与MagicSlider一起使用的方法以及两者都可以使用的方法进行分组的方法。这些对象共享一些方法,因为它们都是Magic gui对象。

我可以通过以特殊方式命名函数MagicSlider_DoSomething ...来进行分组,方法是将方法包含在以特殊方式命名的特定文件MagicSliderMethods.XXX中,或者我可以找到其他一些特殊方式来做同样的事情。如果没有标准的语言方式可以做到这一点,那么我将与您有所不同,并且与其他任何人也有所不同。这使得共享代码变得更加困难。取决于谁编写代码,您最终会在同一程序中使用不同的OO实现。此人必须管理不同的对象抽象和不同的方法来调用虚拟方法,具体取决于谁编写了原始代码。

另外:使用该语言的抽象可以使高级代码编辑器(例如Eclipse)对代码进行大量静态分析。例如,Eclipse可以提供可用于对象的所有方法的列表,以及自动实现空的“ TODO方法”。 Eclispe根据扩展的类和实现的接口确切地知道您的类必须实现的方法。如果没有用于OO的语言标准,这几乎是不可能的。

评论


经典示例:Lua。它不是本地的OO,而是可以实现的,但这意味着大约有5个不同的,同样众所周知的OO库,它们并不是完全可以互操作的。

– Kroltan
17年3月20日在16:17

@steakexchange您过多地关注绝对值。很少有“唯一目的”。语言都会做很多不同的事情以达到不同的质量。选择一种语言是在选择一组权衡因素,使其最适合您所需的目的。

– Tim B
17 Mar 20 '17 at 17:46

@nocomprende标准化抽象几乎就是编程语言的用途。甚至汇编语言也可以抽象化十年半中十多个硬件世代之间的差异。

– David Moles
17年3月20日在22:17

@DavidMoles标准化抽象实际上就是编程语言。不要浪费一个绝佳的机会来按字面意义使用“按字面意思”!

– Clement Cherlin
17 Mar 20 '17在23:01

可以对此进行标准化。上世纪90年代中期,当我在大学时,我在X-Windows上做了相当多的工作(主要是基于Motif的,用于那些记得此类事情的人)。 X-Windows确实确实允许您在常规C语言中实现面向对象的所有功能。尽管如此,精神体操还是相当充实的,并且严重依赖于不在框内寻找东西的人(此时,施罗丁格的Widget代码通常以失败告终。 )。 OO语言与常规编译器对汇编器的作用方式相同,对编码器而言是隐藏起来的,并且生活更轻松。

–格雷厄姆
17年3月21日在16:49

#2 楼


在第一学期中,我们通过Java和UML介绍了OOP概念,例如封装,数据隐藏,模块化,继承等。 (Java是我的第一种编程语言)


这些都不是OOP概念。它们都存在于OO之外,独立于OO,甚至很多都是在OO之前发明的。程序语言的一部分,因为它们与OO无关。例如,关于模块化的开创性论文之一是关于将系统分解为模块的标准。那里没有提及OO。 (它写于1972年,尽管OO已经有10多年的历史了,但OO仍然是一个晦涩的领域。) OO(消息传递)功能比它是一个定义功能。另外,记住有不同类型的数据抽象也很重要。当今使用的两种最常见的数据抽象类型(如果我们忽略“什么也没有抽象”(可能仍比其他两种方法组合使用的次数更多))是抽象数据类型和对象。因此,仅需说“信息隐藏”,“封装”和“数据抽象”,您就对OO没说什么,因为OO只是数据抽象的一种形式,而实际上这两种形式却根本不同:

对于抽象数据类型,抽象的机制是类型系统。隐藏实现的是类型系统。 (类型系统不必一定是静态的。)使用对象时,实现隐藏在过程接口的后面,该过程不需要类型。 (例如,可以使用闭包来实现,就像在ECMAScript中一样。)
使用抽象数据类型,不同ADT的实例彼此封装在一起,但是同一ADT的实例可以检查和访问彼此的表示形式和私有实现。对象始终封装在所有内容中。只有对象本身可以检查其自己的表示形式并访问其自己的私有实现。没有其他对象,甚至没有相同类型的其他对象,相同类的其他实例,具有相同原型的其他对象,该对象的克隆或任何可以执行的操作。没有。

顺便说一句,这意味着在Java中,类不是面向对象的。同一类的两个实例可以访问彼此的表示形式和私有实现。因此,类的实例不是对象,它们实际上是ADT实例。但是,Java interface确实提供了面向对象的数据抽象。因此,换句话说:Java中只有接口实例才是对象,而类实例则不是。

基本上,对于类型,只能使用接口。这意味着方法和构造函数的参数类型,方法的返回类型,实例字段,静态字段和局部字段的类型,instanceof运算符或强制转换运算符的参数以及泛型类型构造函数的类型参数必须始终为接口。一个类只能在new运算符之后直接使用,否则不能使用。


例如,对于模块化,我们可以将程序划分为许多小程序,这些小程序执行定义明确的任务,其代码包含在单独的文件中。这些程序将通过其定义明确的输入和输出相互交互。可以对文件进行保护(加密?)以实现封装。为了重新使用代码,我们只要在新程序中需要它们时就可以调用它们。这不是捕捉所有的OOP还是我遗漏了很明显的东西?


您描述的是OO。

这确实是考虑面向对象的好方法。实际上,这几乎就是OO的原始发明者所想到的。 (Alan Kay进一步走了一步:他设想了许多小型计算机通过网络互相发送消息。)您所说的“程序”通常称为“对象”,而通常不是“呼叫”,而是“发送消息”。 “。

对象定向是关于消息传递(也称为动态调度)的。 “面向对象”一词是Smalltalk的首席设计师Alan Kay博士提出的,他对它的定义如下:以及隐藏状态过程,以及万物的极端后期绑定。


让我们分解一下:派遣”,如果您不熟悉Smalltalk)
应将状态进程


本地保留
受保护
隐藏


所有事物的极端后期绑定

在实现方面,消息传递是后期绑定的过程调用,如果过程调用是后期绑定的,那么您在设计时就无法知道您将要调用的内容,因此您无法对状态的具体表示做任何假设。因此,实际上是关于消息传递的,后期绑定是消息传递的实现,而封装是它的结果。

他后来澄清说,“大想法是'消息传递'”,他很遗憾之所以称其为“面向对象”而不是“面向消息”,是因为“面向对象”一词将重点放在不重要的事物(对象)上,而分散了真正重要的内容(消息): />
轻轻提醒一下,我在上一个OOPSLA上付出了一些努力,试图提醒大家,Smalltalk不仅不是其语法或类库,甚至也不是类。很抱歉,我很早以前就为该主题创造了“对象”一词,因为它使许多人专注于较小的想法。

最大的想法是“消息传递”-这就是Smalltalk / Squeak的核心所在(这在我们的Xerox PARC阶段中从未完全完成)。日语有一个小字-ma-代表“介于两者之间的事物”,也许最接近的英语是“ interstitial”。制作出色且可扩展的系统的关键在于设计模块的通信方式,而不是设计其内部属性和行为。想想互联网-要生存,它(a)必须允许超出任何单一标准的许多不同种类的想法和实现,并且(b)允许这些想法之间具有不同程度的安全互操作性。 />
(当然,今天,大多数人甚至不专注于对象,而是专注于类,这甚至是错误的。)作为一种机制。

如果您向某人发送消息,您将不知道他们如何处理该消息。您唯一可以观察到的就是他们的回应。您不知道他们是否自己处理了消息(即对象是否具有方法),是否将消息转发给其他人(委托/代理),甚至他们是否理解。这就是封装的全部内容,这就是OO的全部内容。您甚至无法区分代理与真实对象,只要它能响应您的期望即可。呼叫”,但是却失去了隐喻,只关注机制。基本上是一个后期绑定过程调用,而后期绑定则意味着封装,因此我们可以得出结论,#1和#2实际上是多余的,而OO都是关于后期绑定的。但是,他后来澄清,重要的是消息传递,因此我们可以从另一个角度看待它:消息传递是后期绑定。现在,如果消息传递是唯一可能的事情,那么#3将是正确的:如果只有一件事,并且那件事是晚绑定的,那么所有事物都是晚绑定的。再次,封装是从消息传递而来的。面向”:


动态分配操作是对象的基本特征。这意味着要调用的操作是对象本身的动态属性。操作不能被静态地识别,并且通常没有办法确切地知道响应给定请求将执行什么操作,除非运行它。这与始终动态分配的一流函数完全相同。


在Smalltalk-72中,甚至没有任何对象!只有消息流被解析,重写和重新路由。首先出现的是方法(解析和重新路由消息流的标准方法),后来出现的是对象(共享某些私有状态的方法组)。继承要晚得多,而引入类只是作为支持继承的一种方式。如果Kay的研究小组已经对原型有所了解,那么它们可能根本不会引入类。 br />
因此:根据Alan Kay的说法,面向对象就是消息传递。根据William Cook所说,面向对象是关于动态方法分配的(这实际上是同一件事)。根据本杰明·皮尔斯(Benjamin Pierce)的观点,面向对象是关于开放递归的,这基本上意味着自我引用是动态解析的(或者至少是一种思考的方式),或者换句话说,是消息传递。正如您所看到的,创造“ OO”一词的人对对象有一种形而上的形而上学的观点,Cook则具有相当务实的观点,而Pierce则具有非常严格的数学观点。但是重要的是:哲学家,实用主义者和理论家都同意!消息传递是OO的支柱之一。句点。

请注意,这里没有提及继承!对于OO而言,继承不是必不可少的。通常,大多数OO语言都具有某种实现重用的方法,但不一定必须是继承。例如,它也可以是某种形式的委托。实际上,《奥兰多条约》讨论了委托作为继承的一种替代方法,以及不同形式的委托和继承如何在面向对象语言的设计空间内导致不同的设计要点。 (请注意,实际上,即使在支持继承的语言(如Java)中,人们实际上也被教导要避免使用继承,这再次表明对OO而言是不必要的。

评论


+100-我们将内在的责任归咎于事物,因为它们的使用不当。

– JeffO
17年3月20日在11:50

抱歉,这是如此令人难以置信的错误。艾伦·凯(Alan Kay)可能想出了这个用语,但原理出在Smalltalk之前。面向对象的编程源于Simula,其OO风格与“消息”无关。几乎每一种成功的OO语言都基于Simula中阐述的基本原理(我们在Java中也看到了相同的原理)运行,而Smalltalk风格的OO在每次重新引入“思想市场”时都是失败的,因为它根本无法很好地工作。名称是Kay所做的唯一真正有意义的事情。

–梅森·惠勒
17年3月20日在12:57

@steakexchange否。“ OO的本质”,使其真正与众不同的是具有虚拟方法的对象。没有人使用Smalltalk是有原因的:消息传递系统在个人计算机规模上运行非常差。每当有好主意但幼稚的语言设计师尝试重新实现Smalltalk原则时,它都会失败。 (最近的例子是Objective-C,如果史蒂夫·乔布斯(Steve Jobs)没把它推到整个iOS社区的喉咙,没人会用过。它从未在Apple生态系统之外找到任何吸引力,这是有原因的。 )

–梅森·惠勒
17 Mar 20 '17 at 13:46

@MasonWheeler您是否可以在回答中详细说明您的评论,因为您与Jorg的说法截然相反?

–steakexchange
17 Mar 20 '17在13:54



还值得一提的是,面向对象语言的概念发展了很多。如今,由于许多语言放弃了旧模型并采用了多范式,因此这些祖先的概念可能不那么正确。例如,以C#为例-这种语言一次混合了几乎所有在阳光下的东西,并且虽然通常被称为OO语言,但实际上是不同范例的混合。这使它成为周围开发人员真正具有表现力的工具。同样,基于类的OO是许多同样有效的OO编程形式之一。

– T. Sar
17 Mar 20 '17在14:47



#3 楼


但是我认为用于管理复杂性的所有原理,例如模块化,封装,数据隐藏等,都可以很容易地由过程语言实现。


当您说“非常容易”时,您的发言就非常大胆。我的阅读方式是:“我看不到困难,所以它一定不能太大。”用这种方式表达时,很明显您不是在问“我们为什么需要OO”,而是在问“为什么其他编程范例遇到的导致OO发明的困难对我来说不是立即显而易见的? “

这个问题的答案是,您正在开发的程序中并不存在许多此类困难。您无需更新40岁的意大利面条代码。您没有尝试为操作系统编写新的显示管理器。您不是在调试多线程分布式应用程序。

对于CS学员负责编写的许多玩具程序,我们也可以像Java或Python一样用BASIC或汇编语言编写。这是因为任务固有的复杂性非常低,只有一名开发人员,没有遗留的互操作性问题,性能也没有关系,并且代码可能只在一台机器上运行几次。 br />
想象一下,带一个学生司机,要求他们在高峰时间合并在一条繁忙的街道上,没有同步的手动变速器,驶向陡峭的山坡。灾害。为什么?他们无法管理同时遵循任务要求的所有规则所需的复杂性级别。他们没问题,因为他们的技能水平足以胜任这项任务。没有压力,风险很小,它们可以承担启动,抓紧,换挡,加速,转向的单个子任务。

那位学生可能会问,如果熟练的驾驶员可以同时完成所有这些事情,为什么我们要有自动变速箱?答案是,熟练的驾驶员在最佳条件下不需要自动挡。但是我们并不是所有人都处于顶峰状态,所以我们通常希望让汽车的设计师为我们解决所有这些复杂性带来的便利。

熟练,训练有素的程序员确实可以创建一个在C或汇编中运行高复杂度系统。但是我们并不都是莱纳斯·托瓦尔兹。我们也不必创建有用的软件。

我个人对在必须解决当前问题之前重新发明现代语言的所有功能没有兴趣。如果我可以利用包含解决问题的解决方案的语言的优势,为什么不呢?和多态,为什么我们不应该使用它们呢?

评论


因此,基本上可以使用一种过程语言来进行OO,但这是在使用标准化OO语言实现自动化和简化的同时手动进行的事情。

–steakexchange
17年3月20日在16:43

@steakexchange差不多就是这个。

– Tim B
17年3月20日在17:44

@steakexchange就是一个很好的历史例子,它可以追溯到X Windows的对象模型。它是用C编写的,但是基于面向对象的系统。因此,您必须遵循某些约定才能与之交互,这样您的类才能与其他所有人一起玩。

– Ukko
17年3月20日在21:09

@nocomprende当然。但是,通过执行原始磁盘写入而不是依赖文件系统,可能使计算机无法启动,并且在新手构建的临时对象系统中调试问题确实非常困难。正确完成封装后,封装将使我们避免有意无意地干预我们不应该做的事情。

– Clement Cherlin
17 Mar 20 '17 at 22:58

对我来说很有趣的是,您给出的从OO中受益的应用程序示例通常不是用OO语言编写的。 40岁的意大利面条可能用C,COBOL,FORTRAN或REXX编写。显示管理器可能使用C语言编写(尽管具有OO-ish约定),许多成功的分布式多线程系统都使用Erlang,Go或C语言编写。

–James_pic
17年3月21日在12:18

#4 楼

您所描述的不是OOP,而是抽象。在所有现代设计模型中都存在抽象,即使不是OOP的模型也是如此。而且,OOP是一种非常特定的抽象。

首先,值得注意的是,没有OOP的单一定义,因此可能有人反对我所描述的OOP。 />
其次,重要的是要记住,OOP受传统设计模型的启发,所以与汽车设计的相似之处并非巧合。您所说的:封装:这不仅仅具有为模块设置接口(即抽象),而且还禁止对该接口的访问。在Java中,访问私有变量是编译错误,而在汽车设计中,您可以(在某些情况下)以与预期接口不同的方式使用事物。

继承:这确实是使OOP独特的东西。定义接口后,您可以使该接口实现多种功能,并且可以采用继承的方式进行,更改其实现的特定部分,同时继承之前的所有部分,从而大大减少了代码重复。

如果您从汽车的封装组件的角度考虑,并没有真正等同的含义。我没有办法采用其他手段并更改其实现的特定部分来做出调整。 (至少我不这么认为,我对汽车的了解也不多。)视图可以进行哪些操作,并且您无需知道正在使用哪种实现来使用接口。这是子类型化和Liskov替代原理变得重要的地方。
耦合:OOP的一个关键方面是我们将事物与相同的操作紧密联系在一起,并展开它们可以具有的不同形式。数据与对该数据的操作捆绑在一起。这意味着添加新的数据形式(新的实现)非常容易,但是向接口添加新的操作非常困难(因为您必须更新实现该接口的每个类)。这与功能语言中的代数数据类型相反,后者很容易添加新的操作(您只需编写一个处理所有情况的函数),但很难添加新的变体(因为您需要添加新的所有功能的大小写)。


评论


好答案!我不同意的一部分:关于封装的区别是无效的。封装总是意味着“禁止访问此接口” –对于OOP和非OOP设置都是如此。因此,这部分并不是OOP特有的。

– D.W.
17年3月20日在16:05

@ D.W。我试图澄清这一点,并不是说它是OOP所独有的,而是封装和抽象之间的区别。感谢您的反馈!

– jmite
17 Mar 20 '17在17:47

好。但是,对于此处关于该主题的内容,我仍然持有不同的看法。您写道:“ OOP在某些方面比您所说的更加细微差别”,但是封装并不是OOP比问题中所写的更加细微差别。封装就是任何范式。在您写到“您描述的不是OOP,而是抽象”的地方,我认为最初的问题是试图描述封装(而不仅仅是抽象)。我想我将以不同的观点发表此评论。我认为答案非常有帮助!

– D.W.
17 Mar 20 '17 at 17:56

继承是一个常见的功能,但是一些重要的OO语言缺少它。

–黄铜Szonyeg
17 Mar 23 '17 at 2:34

很好的答案,但IMO夸大了汽车示例。给定模型的发动机具有明确定义的接口(凸轮轴,安装支架“插座”等)。您可以将普通的老式化油器替换为喷油的,更换涡轮增压器等,而不影响变速箱。 (尽管柴油发动机确实需要改装的燃油箱IIRC。)相反,您可以将手动变速箱替换为完全不影响发动机的自动和AFAIK。

–大卫
17 Mar 23 '17 at 13:03

#5 楼


我们是否真的需要OO语言来管理软件复杂性?

这取决于“需要”一词的含义。
如果“需要”意味着需要,那么我们不这样做不需要。
如果“需要”的意思是“提供强大的好处”,那么我会说“是的”,我们希望得到它。 br />您可以避免这种绑定,并编写传递数据值的函数。
但是您可能会结结在一起的数据星座,然后开始传递元组,记录或字典。实际上,所有方法调用都是:绑定数据集上的部分函数。
按功能划分的特征
OOP的特征:


>继承允许代码(混合)和概念(抽象基类/接口)的重用-但是您可以通过在子作用域中重新定义
函数和变量来实现。

封装允许隐藏信息,以便我们可以进行更高级别的抽象-但是您可以使用头文件,
函数和模块来做到这一点。

多态性允许我们使用不同类型的参数,只要这些参数支持相同的接口-但是我们可以
也可以使用功能。

但是,这些事情都不会像使用具有这些功能的一流支持的面向对象语言那样容易地发生。
参考文献
OOP的批评家很多。这是一个有争议的发现,一些研究人员说,鉴于某些限制,他们无法重现这些生产率的提高。 (源)
结论
我们不需要“ OOP”。但是在某些情况下,用户需要OOP。
我的理解是,成熟的程序员在面向对象的风格上可以非常有生产力。而且,当程序包的核心对象具有易于理解的简单接口时,即使是新程序员也可以迅速提高生产力。

#6 楼

我会尽量简短。

OO的核心原理是将数据和行为结合在一个组织单位(一个对象)中。控制复杂性,当它出现时,这是一个非常新颖的概念。一方面将其与文件(纯数据)进行比较,另一方面将程序读取和处理这些文件(纯逻辑)并输出(再次提供纯数据)。

只有拥有该软件包后,将数据和逻辑结合在一起,对一些真实世界的实体进行建模,您可以开始交换消息,创建子类,将私有数据与公共数据和行为分开,实现多态行为,以及所有这些面向对象的魔术。

因此,是的,OO很重要。不,这不只是一堆名字花哨的旧东西。

将它们全部拆开,看一看元素,然后说:“哦,好了,这里没什么我看不见的之前”是不承认进行创新的程序集。结果超过了各个部分的总和。

#7 楼

没有面向对象程序设计的“正式”定义,并且理性的人在实际定义OO质量上存在分歧。有人说消息传递,有人说子类型化,有人说继承,有人说数据和行为的捆绑。这并不意味着该术语是没有意义的,只是您不应该为真正的OO是什么而过于纠结。

封装和模块化是设计的更基本原理,应将其应用于所有编程范例。 OO的支持者并没有声称只能用OO才能实现这些特性-只是OO特别适合实现这一目标。当然,其他范例的支持者,例如说函数式编程,也声称它们的范例相同。实际上,许多成功的语言都是多范式,并且应该将OO,功能等视为工具,而不是“一种唯一的方法”。


我认为用于管理的所有原则可以通过过程编程语言实现复杂性。


正确,因为最终您可以使用任何编程语言执行任何操作。在某些语言中,这可能比在其他语言中更容易,因为所有语言都有各自的优缺点。

#8 楼

还没有提到其他答案:状态。您将OO作为管理复杂性的工具。有什么复杂性?这是一个模糊的术语。我们都对它的含义有完形的理解,但是很难将其固定下来。我们可以测量循环复杂性,即通过代码的运行时路径的数量,但是我不知道这就是我们使用OO管理复杂性时要讲的。我们谈论的是与状态相关的复杂性。

封装背后有两个主要思想。其中一个,隐藏实现细节,在其他答案中很清楚。但是另一个是隐藏其运行时状态。我们不会随意处理对象的内部数据。我们传递消息(或调用方法,如果您更喜欢实现细节而不是概念,如JörgMittag指出的那样)。为什么?

人们已经提到过这是因为您不能在不更改代码访问权限的情况下更改数据的内部结构,而是想在一个地方(访问器方法)而不是300个地方

但这也是因为它使代码难以推理:过程代码(无论是本质上是过程语言还是简单地以这种样式编写的语言)对于施加对突变的限制都无济于事状态。任何事物都可以随时随地发生变化。调用函数/方法可能会产生怪异的远距离动作。自动化测试更加困难,因为测试的成功取决于广泛访问/可访问的非局部变量的值。

另外两个大型编程范例(OO和函数)提供了有趣的功能,但几乎与国家相关的复杂性问题截然相反。在函数式编程中,人们试图完全避免使用它:函数通常是纯函数,对数据结构的操作将返回副本,而不是就地更新原始副本,等等。

另一方面,OO提供了用于管理状态的工具(而不是用于避免状态的工具)。除了语言级别的工具(例如访问修饰符(受保护的/公共/私有的),getter和setter等)外,还有许多相关的约定,例如Demeter法则,建议不要通过对象来获取其他对象数据。

请注意,您实际上不需要对象即可执行任何操作:您可以拥有一个闭包,该闭包包含无法访问的数据并返回函数的数据结构来对其进行操作。但这不是一个对象吗?从直觉上讲,这不符合我们对物体的概念吗?而且,如果我们有这个概念,那么用语言重新对其进行验证是否更好,而不是(如其他答案所言)依靠竞争性临时实现的组合爆炸式发展?

#9 楼


我们真的需要OO语言来管理软件复杂性吗?


否。但是它们可以在许多情况下提供帮助。

几十年来,我主要使用一种OO语言,但是我的大多数代码实际上都是严格的OO风格的程序。但是,对于涉及GUI的任何事情,我都使用该语言的庞大的OO方法内置的方法和对象,因为它极大地简化了我的代码。用于显示表单,按钮和编辑字段的API需要大量代码,而使用Visual Basic或C#或Delphi附带的对象库会使同一程序变得小而琐碎。因此,我的面向对象的代码通常相对较小,并且对于GUI而言,而那些对象调用的代码通常要大得多,并且通常与面向对象无关(尽管它可以根据我要解决的问题而有所不同)。 br />
我看到过OO程序过于复杂,依赖于关于对象如何实现的复杂神秘规则,如果没有OO概念编写的话,它可能会简单得多。我也看到了相反的情况:复杂的系统迫切需要通过使用对象来重新实现和简化。

随着经验的积累,您会发现不同的情况需要使用不同的工具和解决方案,而一个尺寸并不需要适合所有人。

#10 楼

作为从事完全用C语言编写的大型项目的人,我可以肯定地说答案是明确的“否”。

模块化很重要。但是模块化几乎可以用任何体面的语言实现。例如,C支持模块化编译,头文件和结构类型。这足以满足99%的情况。为所需的每种新抽象数据类型定义一个模块,并定义用于对该数据类型进行操作的功能。有时,您需要性能,而这些函数作为内联函数存在于头文件中,而其他时候,您将使用标准函数。选择哪种方式对用户都是不可见的。

结构支持组合。例如,您可以拥有一个由互斥锁和常规哈希表组成的锁定哈希表。这不是面向对象的编程。没有子类化。组合是一种比面向对象编程的思想更古老的工具。

在1%的情况下,编译级模块化还不够,而您需要运行时模块化,有一种东西叫做函数指针。它们允许具有明确定义的接口的单独实现。请注意,这不是非面向对象语言的面向对象编程。这是定义一个接口,然后实现它。例如,这里不使用子类化。

考虑其中可能是最复杂的开源项目。即,Linux内核。它完全用C语言编写。它主要通过使用标准的编译级模块化工具(包括组合)来完成,然后偶尔需要运行时模块化时,使用函数指针来定义和实现接口。

如果您尝试在Linux内核中找到面向对象编程的示例,那么我肯定很难找到这样的示例,除非您将面向对象编程扩展为包括诸如“定义接口然后实现”之类的标准任务。

请注意,即使您确实需要C编程语言,它也支持面向对象的编程。例如,考虑GTK图形用户界面工具箱。它实际上是面向对象的,尽管是以非面向对象的语言编写的。因此,这表明您需要一种“面向对象的语言”的想法存在严重缺陷。面向对象的语言没有其他语言无法做到的。此外,如果您是专家程序员,就会知道如何轻松地以任何语言编写面向对象的代码。例如,使用C并不是负担。

因此,结论是,面向对象的语言可能仅对不了解该概念实际实现方式的新手程序员有用。但是,我不想在程序员是这样的新手程序员的任何项目附近。

评论


“结论是面向对象的语言可能仅对不了解该概念实际实现方式的新手程序员有用。”有趣。您想使用什么语言?您是否有用这些语言编写的开源项目失败或没有失败的示例?

– Vincent Savard
17 Mar 22 '17在15:52

您提出了一些好的观点,但是您的主要想法存在缺陷。是的,可以使用诸如C的语言来实现OO概念,例如封装,虚拟分派,继承甚至是垃圾回收。也可以在汇编中实现。它不会使编程变得容易。用C之类的语言进行编程,尤其是设计,绝对比使用OO语言困难。在C语言中,您需要将概念映射到其实现,而无需使用OO语言(至少对于OO概念而言)。

–fishinear
17 Mar 22 '17 at 17:23

“ [函数指针]允许单独使用一个定义良好的接口。请注意,这不是用非面向对象的语言进行的面向对象编程。这是定义一个接口,然后实现它。”抱歉,但这是完全错误的,因为这正是OOP。 “例如,这里不使用子类化”子类化不是OOP的必需功能。请注意,例如,JavaScript是一种面向对象的语言,不具有子类化(或就此而言,根本没有类……只是包含函数引用的对象)。

–法律
17 Mar 23 '17 at 10:06

为了澄清我的最后一句话,我的观点是OOP(不一定是任何特定的OO语言)与其他方法之间的主要区别是,在OOP中,我们定义了对数据进行抽象操作的接口,而无需将接口的实现绑定到数据本身。那就是OOP。实现的方法无关紧要,无论是Java样式类,JavaScript对象(实际上是名称到属性的映射,可以是数据还是代码)还是包含函数指针和数据的void *的结构。

–法律
17 Mar 23 '17 at 10:34

“因此,结论是,面向对象的语言可能仅对不了解该概念实际实现方式的新手程序员有用。” ....坦率地说,这是很侮辱。我非常了解如何实施这些概念。我已经做了足够的工作,然后才能精通。过去,我甚至为OO语言实现了解释器和编译器。但是因为我更喜欢使用具有一流对象的高级语言来工作,所以我必须是一个新手程序员,而您不希望与之合作?

–法律
17 Mar 23 '17 at 10:47



#11 楼

引入编程范式(包括面向对象的方法)的原因是使创建更复杂,功能更强大的程序变得更加容易。在1981年8月发行的Byte Magazine中,Smalltalk的主要创建者之一Daniel Ingalls将“面向对象”定义为具有以下功能:自动存储管理交换消息的能力
适用于语言所有操作的统一隐喻
没有组件取决于另一个组件的内部(模块化)
程序仅定义对象的行为,而不定义对象的表示方式
(多态性)
每个组件应仅出现在一个位置(分解)
使用与硬件无关的虚拟机
每个用户可访问的组件都应可用于/>观察和控制(反应性原理)
应该没有整体控制器(没有操作系统)

这些是Ingalls所确定的原理,它们是SmallTalk-80的驱动设计考虑由Xerox Parc Research提供。在前面提到的杂志文章中,您可以阅读有关这些原理的详细说明,以及根据Ingalls的理解它们如何对面向对象的范例作出贡献。语言,无论是程序语言,汇编语言还是其他语言。这些是设计原则,而不是语言规范。面向对象的语言旨在使创建软件时更容易使用这些原理。

例如,要采用Ingall的第一原理(自动存储管理),任何人都可以用过程语言编写自己的自动存储管理系统,但是要做很多工作。当使用诸如SmallTalk或Java之类的内置自动存储管理的语言时,程序员不必做太多的内存管理工作。折衷方案是程序员对内存使用方式的控制较少。因此,存在优点和缺点。像面向对象编程这样的设计范式的思想是,至少对于某些程序员而言,范式的好处将大于弊端。

评论


我认为相同的规则将适用于领域特定语言。相同的优点和缺点...一个不同之处在于,可以使DSL足够简单,以供最终用户使用,因为“语言”对应于他们对问题空间的理解,没有其他内容。

–user251748
17 Mar 23 '17 at 15:33

#12 楼

一种管理软件复杂性的方法是完全使用领域特定语言将框架与所需的操作分开。这意味着编程代码的级别与配置所需结果的级别不同-完全不同的语言或系统。正确完成此操作后,常规代码实际上将变成一个库,并且用户或其他创建所需结果的人会将其与脚本语言或可视化设计工具(例如报告生成器)一起插入。为了工作,这需要围绕可能的操作及其链接方式(脚本语言或可视化设计,例如表单构建工具)划定严格的界限。元数据是一种从编码细节中抽象出运行时配置的重要方式,这使得系统可以支持各种期望的结果。如果对边界进行了布局并坚持(不接受随之而来的每一个扩展请求),那么您就可以拥有一个持久而强大的系统,该系统可以为人们服务,而无需他们成为程序员来完成他们想要的事情。 >
马丁·福勒(Martin Fowler)撰写了有关此书,该技术几乎与编程本身一样古老。您几乎可以说所有编程语言都是领域特定语言,因此这个想法很普遍,因为它很明显而被忽略了。但是,您仍然可以创建自己的脚本或可视化设计工具来简化生活。有时将问题泛化会使解决起来容易得多!

#13 楼

这是一个非常好的问题,我觉得这里给出的答案还没有道理,因此我将继续提出自己的想法。

目标是-管理软件复杂性。目的不是“使用OO语言”。

引入新范式的背后没有“理由”。随着编码变得更加成熟,这是自然发生的事情。编写代码比在链接列表的末尾添加一个新节点更有意义。在火车的末尾添加一个教练(该火车正在使用链接列表建模),而不是在链接列表的末尾添加一个新节点。


用真实世界中的实体进行编码只是当我们对真实世界中的实体进行编码时显而易见的正确方法。尽可能轻松地将其添加到链接列表的末尾,方法是在火车末尾添加一个额外的教练。但是,对于人类来说,与火车和教练的合作比与链表和节点的合作要容易得多,即使当我们深入研究时,我们发现火车是通过链表进行建模的。

保护或加密文件无法实现封装。加密的反义词是解密。封装的反义词是Decapsulation,它意味着用编程语言分解结构和类以获得更好的性能。通过减少内存流量和避免OOP规则检查而获得的性能。

由于这两个是不同的概念,因此您可以编写经过加密和良好封装的代码。

封装有助于

因此,在对象中进行编程是因为,它使您更容易编写代码,并且使您和其他所有人都更快理解。 />

#14 楼

要记住的一件事是:OOP与语言功能无关;

OOP是一种思考和设计代码架构的方法,几乎​​可以使用任何一种语言来完成。这尤其包括那些称为汇编程序和C的低级,非OO语言。您可以在汇编程序中完美地进行面向对象的编程,而用C编写的Linux内核在许多方面都是面向对象的。

也就是说,一种语言中的OO功能大大减少了为达到所需结果而需要编写的样板代码。在需要显式定义虚拟函数表并在C中用适当的函数指针填充它的地方,您只需在Java中什么都不做,就可以完成。 OO语言只是从源代码中删除了所有使能的东西,将其隐藏在良好的语言级别的抽象(如类,方法,成员,基类,隐式构造函数/析构函数调用等)之后。 >因此,不,我们不需要OO语言来进行OOP。仅仅用一种体面的OO语言可以轻松实现OOP。

#15 楼

面向对象的编程不仅仅是模块+封装。如您所说,可以在非面向对象(过程)语言中使用模块+封装。 OOP所涉及的不仅是:它涉及对象和方法。因此,不,这不能捕获OOP。参见例如https://en.wikipedia.org/wiki/Object-oriented_programming或有关OOP的优秀教科书介绍。

评论


谢谢您的回答。你能推荐一个吗?

–steakexchange
17 Mar 20 '17在9:53

#16 楼

最大的原因是,随着程序变得越来越复杂,您需要使该程序的某些部分与其他部分不可见,否则该应用程序的复杂性和功能的数量将使您的大脑从耳边运出。

让我们想象一个由100个类组成的系统,每个类可以执行约20种操作;那是2,000个函数但是,在这些功能中,也许只有500项是完整的操作,例如“保存”和“删除”,而1500项是内部功能,它们需要进行一些维护或具有某些实用性。考虑;
// intentionally in a non-specific language!

setName(person, name) {
    nameParts = splitPersonName(name);
    person.firstName = nameParts[0];
    person.lastName = nameParts[1];
    person.modified = true;
}

splitPersonName(name) {
    var result = [];
    result.add(name.substring(0, name.indexOf(" ")));
    result.add(name.substring(name.indexOf(" ") + 1));
    return result;
}
是人们应该对人执行的功能,而SetName是该人使用的效用函数。

直接过程编程在这两个操作之间没有区别。这意味着您的2,000个功能都在争夺您的注意力。但是,如果我们可以将这些功能标记为“对拥有个人记录的每个人都可用”,并且“仅用作个人记录内部的效用函数”,那么我们现在的注意力是500个“对每个人都可用”功能,以及15个“实用程序”您正在编辑的班级的功能。

SplitPersonNamepublic就是这样;

public class Person {
    public void setName(...) {...}
    private string[] splitPersonName(...) { ...}
}


评论


我不确定您是否正确理解我的问题。我已经知道封装和数据隐藏是管理复杂性的一种方式。我只是认为,可以通过将程序划分为执行定义明确的简单任务的模块来轻松地用程序语言来完成,这些任务的内部工作在单独的受保护源中指定。那么,为什么我们可以用过程语言来完成这些事情呢?那是我的问题。

–steakexchange
17 Mar 20 '17在10:47

我想我的回答是,如果您想执行此类操作,则必须编写特殊的工具和语言结构(例如,对包含一个文件的特殊“ include”调用可能不会包含在其他位置)。一旦走上了这条道路,您实际上已经开始以自己的方式实现面向对象的语言。例如,C ++最初是产生纯C的预处理器,我怀疑一旦实现系统,它就很像C之上的C ++。

–user62575
17年3月20日在10:58

@steakexchange请注意,OOP是在许多过程语言没有这样的模块的时候开发的。有些人甚至不称此类语言为过程语言。确实,没有什么可以说一种过程语言一定不能是面向对象的,反之亦然。注意标签-它们很容易误导您。如果您的过程语言支持具有公共和私有字段和过程的模块,那么对您有好处:)“传统过程”和“ OOP”之间的主要区别在于,调用分派在OOP中更为灵活-实际上,在严格的OOP中,永远不知道你叫什么代码。

–罗安
17 Mar 20 '17在13:59



@steakexchange在ML系列语言中,模块可以很好地工作,并且与lambda结合使用,它们可以为您提供任何OO语言的所有功能(毕竟,一个函数是一个单一方法的接口,这几乎不是推荐的) OO中的“好代码”家伙?由于各种原因,它们仍然没有像C ++或Java这样的更具过程性的语言少用,但是它们确实具有吸引力,并且许多人正在尝试教育人们如何简化生活(或多或少获得成功)。

–罗安
17 Mar 20 '17 at 14:01

C有效地拥有这些东西。它具有带有接口(.h文件)的模块(.c文件),并且可以具有公共(外部)方法和非公共(外部)方法(功能)。您甚至可以通过函数指针数组实现穷人的多态性,我并不是说OO在C语言中很容易(或者说理智),但是封装非常容易,

–尼克·基利(Nick Keighley)
17 Mar 21 '17在15:46