OOP在实践中解决了哪些程序编程问题?


17

我已经读过《 C ++ Demystified》一书。现在,我开始阅读Robert Lafore的“ Turbo C ++第一版(第1版)中的面向对象程序设计”。除了这些书之外,我没有编程方面的知识。这本书已经有20年历史了,所以可能已经过时了。我确实有最新版本,因为我喜欢旧版本,所以我正在使用旧版本,主要是我只是通过Lafore的第一版研究C ++中使用的OOP的基本概念。

Lafore的书强调“ OOP”仅对较大复杂的程序有用。在每本OOP书中(以及在Lafore的书中)都说过,程序范式容易出错,例如全局数据容易受到功能的影响。据说程序员可以用程序语言编写诚实的错误,例如通过创建一个意外破坏数据的函数。

老实说,我发布问题是因为我没有掌握本书给出的解释:C ++中的面向对象编程(第4版)我没有掌握Lafore本书中写的这些语句:

之所以开发面向对象的程序设计,是因为在早期的编程方法中发现了局限性。...随着程序变得越来越大,越来越复杂,甚至结构化编程方法也开始显示出紧张的迹象....分析原因这些失败表明程序范式本身存在弱点。不管结构化编程方法的实现程度如何,大型程序都变得过于复杂。……有两个相关的问题。首先,功能可以不受限制地访问全局数据。其次,不相关的功能和数据是程序范式的基础,为现实世界提供了糟糕的模型。

我已经学习了Jeff Kent的书“ dysmystified C ++”,我非常喜欢这本书,这本书中主要是对过程编程的说明。我不明白为什么程序(结构化)编程很弱!

Lafore的书通过一些很好的例子很好地解释了这个概念。另外,通过阅读Lafore的书,我已经掌握了一种直觉,即OOP比过程编程要好,但是我很好奇,实际上,过程编程要比OOP弱。

我想看看自己在过程式编程中将面临的实际问题是什么,OOP如何使编程更容易。我想我只是沉思地阅读Lafore的书就能得到答案,但是我想亲眼看看程序代码中的问题,我想看看程序的OOP样式代码如何消除前面提到的错误。使用程序范例编写相同的程序。

OOP有很多功能,我理解没有人可以向我解释所有这些功能如何消除以程序样式编写代码会产生的上述错误。

所以,这是我的问题:

OOP解决了程序编程的哪些限制,并且在实践中如何有效消除这些限制?

特别是,是否存在使用程序范例难以设计但易于使用OOP设计的程序的示例?

PS:交叉发布于:https : //stackoverflow.com/q/22510004/3429430


3
过程编程和面向对象编程之间的区别在某种程度上是表示和强调的问题。大多数自称为面向对象的语言也是程序性的,这些术语着眼于语言的不同方面。
吉尔斯

2
我现在重新提出问题;让我们看看这是怎么回事。请记住,任何提出OOP的方法都是圣杯,解决所有问题的编程语言弥赛亚都是胡说八道。有优点也有缺点。您正在要求专业人士,这是一个公平的问题。不要期望更多。
拉斐尔

设计一个没有OOP的(现代)桌面GUI的想法以及随之而来的一些技术(例如事件/侦听器模式)使我感到恐惧。但这并不意味着它不能完成(它当然可以)。另外,如果您想了解PP在运行中的失败,请查看PHP并将API与Ruby进行比较。
拉斐尔

1
“无论结构化编程方法实现得如何好,大型程序都变得过于复杂……” OOP也是如此,但是如果开发人员以规定的方式应用,它基本上可以更好地管理复杂性……并且它主要是通过更好/更大的范围边界/限制(即一种分隔系统)来实现的... aka APIE:抽象,多态,继承,封装
vzn 2014年

1
同一个问题OOP VS过程编程软件工程
VZN

Answers:


9

在过程语言中,您不一定表达要求调用方以受支持的方式使用模块所必需的限制。在没有编译器可检查的限制的情况下,您必须编写文档并希望遵循该文档,并使用单元测试来演示预期的用途。

声明类型是最明显的声明限制(即:“证明x是浮点数”)。强制数据突变通过已知为该数据设计的功能是另一回事。协议执行(方法调用顺序)是部分受支持的限制,即:“构造函数->其他方法*->析构函数”。

当编译器了解该模式时,还有一个真正的好处(还有一些缺点)。当您从过程语言仿真数据封装时,使用多态类型的静态类型会有点问题。例如:

类型x1是x的子类型,t1是t的子类型

这是一种使用过程语言将数据封装为类型t的方法f和g,以及子类t1的方法也是如此:

t_f(t,x,y,z,...),t_g(t,x,y,...)t1_f(t1,x,y,z,...)

为了按原样使用此代码,必须在确定要调用的f的类型之前进行类型检查并打开t的类型。您可以这样解决:

类型t {d:数据f:功能g:功能}

因此,您改为调用tf(x,y,z),其中类型检查和切换以查找方法的过程现在被替换为仅具有每个实例的显式存储方法指针。现在,如果每种类型都有大量的函数,那么这是一种浪费。然后,您可以使用另一种策略,例如让t指向包含所有成员函数的变量m。如果此功能是语言的一部分,则可以让编译器弄清楚如何处理这种模式的有效表示。

但是数据封装是对可变状态不好的认识。面向对象的解决方案是将其隐藏在方法后面。理想情况下,对象中的所有方法应具有定义明确的调用顺序(即:构造函数->打开-> [读|写]->关闭->析构);有时称为“协议”(研究:“ Microsoft Singularity”)。但是,除了构造和破坏之外,这些要求通常不是类型系统的一部分-或有据可查。从这个意义上说,对象是通过方法调用转换的状态机的并发实例。这样您就可以拥有多个实例,并以任意交错的方式使用它们。

但是在认识到可变的共享状态不好时,可以注意到,由于对象数据结构是许多对象都引用的可变状态,因此面向对象会产生并发问题。大多数面向对象的语言都在调用者的线程上执行,这意味着方法调用中存在竞争条件。更不用说非原子的函数调用序列了。作为替代方案,每个对象都可以在队列中获取异步消息并在对象的线程上(使用其私有方法)为它们提供服务,并通过向其发送消息来响应调用方。

将多线程上下文中的Java方法调用与Erlang进程之间相互发送消息(仅引用不可变值)进行比较。

由于锁定,无限制的对象定向与并行性相结合是一个问题。从软件事务存储(即类似于数据库的存储对象中的ACID事务)到使用“通过通信共享内存(不可变数据)”(功能编程混合)方法,其技术范围很广。

在我看来,面向对象的文献将FAR过多地放在继承上,而在协议上却不够(可检查方法调用顺序,前置条件,后置条件等)。对象消耗的输入通常应具有定义明确的语法,可以表示为类型。


您是说用OO语言,编译器可以检查方法是否按规定的顺序使用,或者对模块的使用有其他限制?为什么“数据封装识别可变状态不好”?当谈到多态时,您是否假设您正在使用OO语言?
2014年

在OO中,最重要的功能是能够隐藏数据结构(即:要求引用来自this.x),以证明所有访问均通过方法进行。在静态类型的OO语言中,您要声明更多的限制(基于类型)。关于方法顺序的注释只是说OO强制首先调用构造函数,最后强制调用析构函数;这是从结构上禁止坏电话订单的第一步。简单的证明是语言设计的重要目标。
罗布

8

程序/函数编程绝不比OOP弱,即使不涉及图灵参数(我的语言具有图灵能力并且可以做其他任何事情都可以做),这没有多大意义。实际上,面向对象技术首先是在没有内置语言的语言中进行实验的。从这个意义上讲,OO编程只是过程编程的一种特定样式。但是,它有助于实施特定的学科,例如模块性,抽象性和信息隐藏,这对于程序的易懂性和维护至关重要。

一些编程范例是从计算的理论视野演变而来的。诸如Lisp之类的语言是从lambda微积分和语言的元循环性(类似于自然语言的自反性)的思想演变而来的。Horn子句继承了Prolog和约束编程。Algol家族还应归功于Lambda微积分,但没有内置的反射性。

Lisp是一个有趣的例子,因为它已经成为许多编程语言创新的试验平台,可以追溯到其双重遗传遗产。

然而,语言随后发展起来,通常使用新名称。进化的主要因素是编程实践。用户确定可以提高程序属性(例如可读性,可维护性,正确性可证明性)的编程实践。然后,他们尝试在语言中添加将支持并且有时实施这些做法的功能或约束,以提高程序的质量。

这意味着这些实践已经可以在较旧的编程语言中使用,但是使用它们需要理解和纪律。将它们作为具有特定语法的主要概念合并到新语言中,使这些做法更易于使用和易于理解,特别是对于不太熟练的用户(即绝大多数)而言。对于老练的用户来说,这也使生活变得更轻松。

在某种程度上,语言设计是程序的子程序/功能/过程。一旦确定了有用的概念,便会给它一个名称(可能)和一个语法,以便可以在使用该语言开发的所有程序中轻松使用它。而且,一旦成功,它也将以未来的语言合并。

示例:重新创建面向对象

我现在尝试通过一个示例来说明这一点(在一定的时间内,当然可以进一步完善)。该示例的目的并不是要表明可以以过程编程的方式编写面向对象的程序,这可能会牺牲其易用性和可维护性。我宁愿尝试证明某些没有OO设施的语言实际上可以使用高阶函数和数据结构来实际创建有效地模仿对象定向的方法,以便从其有关程序组织的质量(包括模块化,抽象和信息隐藏)中受益。。

就像我说的那样,Lisp是包括OO范例在内的许多语言发展的试验台(尽管可以认为第一种OO语言是Algol家族中的Simula 67)。Lisp非常简单,其基本解释器的代码不到一页。但是您可以在Lisp中进行OO编程。您需要的只是高阶函数。

我不会使用深奥的Lisp语法,而是使用伪代码来简化演示。我将考虑一个简单的基本问题:信息隐藏模块化。定义一类对象,同时防止用户访问(大多数)实现。

假设我想创建一个称为vector的类,表示2维矢量,其方法包括:矢量加法,矢量大小和并行性。

function vectorrec () {  
  function createrec(x,y) { return [x,y] }  
  function xcoordrec(v) { return v[0] }  
  function ycoordrec(v) { return v[1] }  
  function plusrec (u,v) { return [u[0]+v[0], u[1]+v[1]] }  
  function sizerec(v) { return sqrt(v[0]*v[0]+v[1]*v[1]) }  
  function parallelrec(u,v) { return u[0]*v[1]==u[1]*v[0]] }  
  return [createrec, xcoordrec, ycoordrec, plusrec, sizerec, parallelrec]  
  }  

然后,我可以将创建的向量分配给要使用的实际函数名称。

[vector,xcoord,ycoord,vplus,vsize,vparallel] = vectorclass()

为什么这么复杂?因为我可以在函数vectorrec中定义中间结构,所以我不想在程序的其余部分可见,以保持模块化。

我们可以用极坐标进行另一个收集

function vectorpol () {  
  ...  
  function pluspol (u,v) { ... }  
  function sizepol (v) { return v[0] }  
  ...  
  return [createpol, xcoordpol, ycoordpol, pluspol, sizepol, parallelpol]  
  }  

但是我可能想分别使用这两种实现。一种方法是将类型组件添加到所有值,并在同一环境中定义所有上述函数:然后,我可以定义每个返回的函数,以便它将首先测试坐标类型,然后应用特定函数为了它。

function vector () {  
    ...  
    function plusrec (u,v) { ... }  
    ...  
    function pluspol (u,v) { ... }  
    ...  
    function plus (u,v) { if u[2]='rec' and v[2]='rec'  
                            then return plusrec (u,v) ... }  

    return [ ..., plus, ...]  
    }

我得到了什么:特定功能仍然不可见(由于本地标识符作用域),并且程序的其余部分只能使用调用vectorclass返回的最抽象的功能。

一个反对意见是,我可以直接在程序中定义每个抽象函数,而不必考虑依赖于坐标类型的函数。然后也将被隐藏。的确如此,但是每种坐标类型的代码将被切成小段,散布在程序中,从而减少了可重复性和可维护性。

实际上,我什至不需要给它们起一个名字,我可以将as匿名函数值保留在由类型和代表函数名的字符串索引的数据结构中。对于函数向量而言,这种局部结构在程序的其余部分中是不可见的。

为了简化使用,我可以返回一个名为apply的函数,而不是返回一个函数列表,该函数以显式类型值和字符串作为参数,并以正确的类型和名称应用该函数。这看起来非常像为OO类调用方法。

我将在此停止对面向对象工具的这种重构。

我试图做的是表明,以足够强大的语言(包括继承和其他此类功能)构建可用的面向对象并不是太难。解释器的元圆度可以提供帮助,但主要是在句法层面上,这仍然可以忽略不计。

面向对象的第一批用户确实以这种方式对概念进行了实验。编程语言的许多改进通常都是这样。当然,理论分析也可以发挥作用,有助于理解或完善这些概念。

但是,没有OO功能的语言注定要在某些项目中失败的想法是毫无根据的。如果需要,他们可以非常有效地模仿这些功能的实现。即使没有内置语言,许多语言也具有语法和语义功能,可以非常有效地进行面向对象的定向。这不仅仅是图灵论据。

OOP并未解决其他语言的局限性,但它支持或强制执行有助于编写更好的程序的编程方法,从而帮助经验不足的用户遵循在没有该支持的情况下,更高级的程序员一直在使用和开发的良好实践。

我相信了解这一切的一本好书可能是Abelson&Sussman:计算机程序的结构和解释


8

我认为是有一段历史的。

从1960年代中期到1970年代中期的时代今天被称为“软件危机”。在1972年的图灵奖演讲中,我不能说比Dijkstra更好:

导致软件危机的主要原因是机器变得功能强大了几个数量级!坦率地说:只要没有机器,编程就完全没有问题。当我们有一些弱小的计算机时,编程成为一个温和的问题,而现在我们有了庞大的计算机,编程也同样成为一个巨大的问题。

当时是第一批32位计算机,第一批真正的多处理器和第一台嵌入式计算机的时代,研究人员清楚地知道,这些对于将来的编程将至关重要。在历史上,客户需求第一次超过了程序员的能力。

毫不奇怪,这是编程研究中非常肥沃的时间。在1960年代中期之前,我们确实有LISP和AP / L,但是从根本上说,“主要”语言是程序语言:FORTRAN,ALGOL,COBOL,PL / I等。从1960年代中期到1970年代中期,我们获得了Logo,Pascal,C,Forth,Smalltalk,Prolog,ML和Modula,并且这还不包括SQL及其前身之类的DSL。

这也是历史上发展用于实现编程语言的许多关键技术的时代。在此期间,我们获得了LR解析,数据流分析,通用子表达式消除以及对某些编译器问题(例如寄存器分配)的NP困难的首次认识,并试图以此解决这些问题。

这就是OOP出现的背景。因此,在1970年代初期回答了您有关OOP在实践中解决什么问题的问题时,第一个答案是它似乎解决了那个时期程序员面临的许多问题(包括当代的和预期的)。但是,这不是OO成为主流的时候。我们会尽快解决。

当艾伦·凯(Alan Kay)提出“面向对象”一词时,他想到的画面是软件系统的结构将像生物系统一样。您将拥有诸如单个细胞(“对象”)之类的东西,它们通过发送类似于化学信号(“消息”)的东西而彼此交互。您无法(或至少不会)在一个单元内凝视;您只能通过信令路径与之互动。此外,如果需要,每种类型的单元中可以有多个。

您会发现这里有一些重要的主题:定义明确的信令协议的概念(在现代术语中是接口),从外部隐藏实现的概念(在现代术语中是隐私)以及在同一时间具有多个相同类型的“事物”(在现代术语中,实例化)。

您可能会注意到缺少的一件事,那就是继承,这是有原因的。

面向对象的编程是一个抽象概念,可以用不同的编程语言以不同的方式来实现抽象概念。例如,“方法”的抽象概念可以在C中使用函数指针实现,在C ++中使用成员函数实现,在Smalltalk中使用方法实现(这应该不足为奇,因为Smalltalk非常直接地实现了抽象概念)。这就是人们指出(非常正确)您可以(几乎)使用任何一种语言“进行” OOP时的意思。

另一方面,继承是一种具体的编程语言功能。继承对于实现OOP系统很有用。至少,直到1990年代初,情况才如此。

从1980年代中期到1990年代中期,这也是历史上变化的时期。在这段时间里,我们有了价格便宜,无处不在的32位计算机的兴起,因此,企业和许多家庭都可以负担得起,在每张桌子上都安装与当今最低端大型机几乎一样强大的计算机。这也是它的鼎盛时期。这也是现代GUI和网络操作系统兴起的时代。

在这种情况下,出现了面向对象的分析和设计。

OOAD,“三个Amigos”(Booch,Rumbar和Jacobson)以及其他人(例如Shlaer-Mellor方法,责任驱动的设计等)的影响不可低估。这就是为什么自1990年代初以来开发的大多数新语言(至少,您听说过的大多数语言)都具有Simula风格的对象。

因此,1990年代您的问题的答案是,它为面向领域的分析和设计方法提供了最佳(当时)解决方案。

从那时起,因为有了锤子,我们将OOP应用于此后出现的几乎所有问题。OOAD及其使用的对象模型鼓励并启用了敏捷和测试驱动的开发,集群以及其他分布式系统,等等。

现代GUI和过去20年来设计的任何操作系统都倾向于以对象的形式提供其服务,因此,任何新的实用编程语言至少都需要一种绑定到我们今天使用的系统的方式。

因此,现代的答案是:它解决了与现代世界接口的问题。现代世界是建立在面向对象之上的,其原因与1880年的世界是建立在蒸汽之上的原因相同:我们了解它,可以控制它,并且可以很好地完成工作。

当然,这并不是说研究到此为止,但它确实表明,任何新技术都将需要OO作为极限案例。您不必是OO,但是您不能从根本上不兼容。


除了我不想在主要论文中提到的一点之外,WIMP GUI和OOP似乎是非常自然的选择。对于深度继承层次结构,可以说很多不好的事情,但这是一种情况(可能是唯一的情况),在这里看来有些道理。
别名2014年

1
在操作系统的内部组织中,OOP首先出现在Simula-67(模拟)中(Unix中的“设备类”的思想本质上是驱动程序继承的类)。帕纳斯(Parnas)的“关于将系统分解为模块的标准”,CACM 15:12(1972),第1052-1058页,七十年代的Wirth的Modula语言,“抽象数据类型”都是一种或多种形式的前身。其他。
vonbrand 2015年

没错,但是我坚持认为,直到70年代中期,OOP才被视为“解决程序编程问题的解决方案”。定义“ OOP”非常困难。艾伦·凯(Alan Kay)最初使用该术语与Simula的模型不一致,不幸的是,世界已经对Simula的模型进行了标准化。一些对象模型具有类似Curry-Howard的解释,但Simula却没有。当Stepanov指出继承不健全时,他可能是正确的。
别名

6

没有,真的。严格来讲,OOP并不能真正解决问题。对于非面向对象的系统,您无法使用面向对象的系统做任何事情-的确,对于图灵机,这是您无能为力的。最终所有这些都变成了机器代码,并且ASM当然不是面向对象的。

OOP范例为您服务的是,它使组织变量和函数变得更加容易,并使您可以更轻松地将它们一起移动。

假设我想用Python编写纸牌游戏。我将如何代表卡?

如果我不了解OOP,可以这样进行:

cards=["1S","2S","3S","4S","5S","6S","7S","8S","9S","10S","JS","QS","KS","1H","2H",...,"10C","JC","QC","KC"]

我可能会写一些代码来生成这些卡,而不是只手工写出来,但是您明白了。“ 1S”代表黑桃1,“ JD”代表钻石杰克,依此类推。我还需要一个小丑代码,但我们只是假装目前没有小丑。

现在,当我想要洗牌时,我只需要“洗”列表即可。然后,要从卡片组顶部取出卡片,我从列表中弹出顶部条目,给我字符串。简单。

现在,如果我想弄清楚我正在使用哪张卡,以便将其显示给玩家,我需要一个像这样的功能:

def card_code_to_name(code):
    suit=code[1]

    if suit=="S":
        suit="Spades"
    elif suit=="H"
        suit="Hearts"
    elif suit=="D"
        suit="Diamonds"
    elif suit=="C"
        suit="Clubs"

    value=code[0]

    if value=="J":
        value="Jack"
    elif value="Q":
        value="Queen"
    elif value="K"
        value="King"

    return value+" of "+suit

有点大,很长且效率低下,但它可以工作(而且非常不可思议,但这不在这里。)

现在,如果我希望卡片能够在屏幕上移动怎么办?我必须以某种方式存储他们的位置。我可以将其添加到他们的卡代码的末尾,但这可能有点笨拙。相反,让我们再列出每张卡的位置:

cardpositions=( (1,1), (2,1), (3,1) ...)

然后,我编写代码,以使列表中每张卡的位置索引与卡组中卡本身的索引相同。

或者至少应该如此。除非我弄错了。我很可能会这样,因为要处理此设置,我的代码将变得相当复杂。当我想洗牌时,我必须以相同顺序洗牌。如果我将卡完全从甲板上取出怎么办?我也必须把它的位置拿出来,放在其他地方。

如果我想存储有关卡的更多信息怎么办?如果我想存储是否翻转每张卡怎么办?如果我想要某种物理引擎并且还需要知道卡片的速度怎么办?我将完全需要另一个列表来存储每张卡的图形!对于所有这些数据点,我将需要单独的代码来将它们正确地组织起来,以便每张卡都以某种方式映射到其所有数据!

现在让我们尝试这种OOP方式。

让我们定义一个Card类并从中构建Card对象列表,而不是代码列表。

class Card:

    def __init__(self,value,suit,pos,sprite,flipped=False):
        self.value=value
        self.suit=suit
        self.pos=pos
        self.sprite=sprite
        self.flipped=flipped

    def __str__(self):
        return self.value+" of "+self.suit

    def flip(self):
        if self.flipped:
            self.flipped=False
            self.sprite=load_card_sprite(value, suit)
        else:
            self.flipped=True
            self.sprite=load_card_back_sprite()

deck=[]
for suit in ("Spades","Hearts","Diamonds","Clubs"):
    for value in ("1","2","3","4","5","6","7","8","9","10","Jack","Queen","King"):
        sprite=load_card_sprite(value, suit)
        thecard=Card(value,suit,(0,0),sprite)
        deck.append(thecard)

现在,突然之间,一切都变得简单了。如果要移动卡,则不必弄清楚卡在卡座中的位置,然后使用该卡将其位置从位置阵列中移出。我只需要说thecard.pos=newpos。当我从主卡组列表中取出卡时,不必创建新的列表来存储所有其他数据。卡对象移动时,其所有属性都随之移动。而且,如果我想要一张在翻转时行为不同的卡,则不必修改主代码中的翻转功能,以便它检测到这些卡并执行不同的操作;我只需要对Card进行子类化,并在子类上修改flip()函数。

但是如果没有OO,我在那做的事就不可能完成。仅仅是使用一种面向对象的语言,该语言正在为您完成将事物保持一致的许多工作,这意味着您犯错误的机会要少得多,并且代码更短,更容易阅读和编写。

或者,总而言之,OO通过隐藏很多处理数据的常见复杂性,使您可以编写看起来更简单的程序,与更复杂的程序执行相同的工作。


1
如果您从OOP中学到的唯一一件事就是“内存管理”,那么我认为您对此不是很了解。这里有一个完整的设计理念,还有一个大瓶的“按设计校正”!同样,内存管理当然不是面向对象(C ++?)固有的,即使它的需求变得更加明显。
拉斐尔

是的,但这是单句版本。我也以相当不标准的方式使用了该术语。也许说“处理信息”比“内存管理”更好。
希尔科特

是否有任何非OOP语言允许函数获取指向某事物的指针,以及指向其第一个参数为指向同一事物的指针的函数的指针,并使编译器验证该函数是否合适传入的指针?
2014年

3

编写嵌入式C已有几年,负责管理设备,串行端口以及串行端口,网络端口和服务器之间的通信数据包等内容;我发现自己是一位训练有素的电气工程师,具有有限的程序编程经验,他从硬件中捏造出我自己的抽象,最终体现在我后来意识到的是普通人所说的“面向对象的编程”中。

当我移到服务器端时,我接触到一个工厂,该工厂在实例化时设置内存中每个设备的对象表示。我听不懂单词或刚开始发生的事情-我只知道我进入了这样命名的文件并编写了代码。后来,我再次发现自己,终于认识到了OOP的价值。

我个人认为这是教授面向对象的唯一方法。我大一的时候就参加了OOP(Java)入门课程,这完全让我感到头疼。在我的拙见中,建立在对小猫->猫->哺乳动物->活着->事物或叶子->分支->树->花园进行分类的基础上的OOP描述绝对是荒谬的方法,因为没有人会尝试解决这些问题问题,如果您甚至可以称他们为问题...

我认为,如果您以不太绝对的眼光看待问题,会更容易回答问题- 不是“问题解决了什么”,而是从“这是问题所在,这就是它使它变得更容易”的角度出发。在我特定的串行端口情况下,我们有一堆编译时#ifdefs,它们添加和删除了静态打开和关闭串行端口的代码。端口打开功能在各处都可以调用,并且可以位于我们拥有的10万行OS代码中的任何位置,并且IDE不会使未定义的内容变灰-您必须手动进行跟踪,然后随身携带。不可避免地,您可能有多个任务试图打开给定的串行端口,期望它们的设备在另一端,然后您刚编写的代码都无法正常工作,并且您不知道为什么。

尽管是C语言,但抽象是一个串行端口“类”(很好,只是一个结构数据类型),我们有一个数组(每个串行端口一个),而不是[串行端口中的DMA等效]从任务直接在硬件上调用“ OpenSerialPortA”,“ SetBaudRate”等函数,我们调用了一个辅助函数,您将所有通信参数(波特,奇偶校验等)传递给了该函数,该函数首先检查结构数组以查看是否端口已经被打开-如果是的话,通过哪个任务,它将告诉您作为调试printf,以便您可以立即跳转到需要禁用的代码部分-如果没有,则继续进行设置所有参数都通过其HAL汇编功能完成,最后打开了端口。

当然,OOP也有危险。当我最终清理完代码库并使所有内容变得整洁干净时-为该产品线编写新的驱动程序最终落入了可计算,可预测的科学之列,我的经理EOL选择了该产品,因为这是他需要的少一个项目管理,他是边缘可移动的中层管理人员。这让我感到很沮丧/我感到非常沮丧,所以我辞掉了工作。大声笑。


1
嗨!这似乎更像是个人经历,而不是问题的答案。从您的示例中,我看到您以面向对象的样式重写了一些可怕的代码,这使它变得更好。但是尚不清楚这种改进是否与面向对象有关,或者仅仅是因为那时您是一个更加熟练的程序员。例如,您的很多问题似乎是由于代码随意散布在该位置而引起的。可以通过编写没有对象的过程库来解决。
David Richerby

2
@DavidRicherby我们有过程库,但这就是我们不赞成使用的库,不仅仅是代码无处不在。关键是我们倒退了。没有人试图对任何东西进行操作,这只是自然而然的事情。
pa增加

@DavidRicherby您可以举任何程序库实现的示例,以便确保我们在谈论同一件事吗?
pa增加

2
感谢您的回答和+1。很久以前,另一位经验丰富的程序员分享了OOP如何使他的项目变得更加可靠Forums.devshed.com/programming-42/…我想OOP是由一些可能在程序方法上遇到一些问题的专业人员非常聪明地设计的。
user31782

2

关于OOP编程在何处/何处比程序编程有优势(包括其发明者和用户)有许多主张意图。而仅仅是因为技术被设计为通过它的设计师一定的目的并不能保证它会成功,在这些目标。这是对软件工程领域的重要理解,可以追溯到布鲁克斯着名的论文“ Nosilver bullet”,尽管OOP编码革命,该论文仍然有意义。(有关新技术,另请参阅Gartner炒作周期。)

许多使用过这两种方法的人也都从轶事经验中获得了意见,这具有一定的价值,但是在许多科学研究中,众所周知,自我报告的分析可能是不准确的。似乎很少有关于这些差异的定量分析,或者如果有的话,则没有太多引用。令人惊讶的是,有如此多的计算机科学家在其领域的某些特定主题上发表了权威性的演讲,却没有引用科学研究来支持他们的观点,却没有意识到他们实际上正在继续前进。沿用该领域的传统智慧(尽管广泛存在)。

由于这是一个科学站点/论坛,因此这里仅作简短尝试,以提出众多观点更加坚定并量化实际差异。可能还有其他研究,希望其他人听到后会指出。(禅宗问题:如果确实存在重大差异,并且已经在商业软件工程领域和其他领域进行了大量努力/投入了大量资金来实现这一点,那么为什么要如此困难地找到科学证据呢?一些经典的,引人注目的参考文献,可以最终量化差异?)

本文采用实验/定量/ 科学分析,并特别支持使用OOP编码方法可以提高新手程序员的理解能力,但在某些情况下(相对于程序大小)并不确定。注意,这仅仅是一个在其他的答案:通过面向对象的倡导者正在推进关于OOP的优势在众多/重大索赔。该研究可能正在测量一种称为 “认知负荷/开销”

  • 新手程序员对面向对象程序和过程程序理解的比较与计算机交互。Susan Wiedenbeck,Vennila Ramalingam,Suseela Sarasamma,Cynthia L Corritore(1999)

    本文报告了两个实验,比较了新手在面向对象和程序样式下的心理表征和程序理解能力。主题是新手程序员,他们选修了第二门编程课程,该课程教授面向对象或过程范式。第一个实验比较了以程序和面向对象风格编写的简短程序的心理表征和理解。第二个实验将该研究扩展到一个包含更高级语言功能的更大程序。对于简短的程序,在正确回答的问题总数方面,两组之间没有显着差异,但是在回答有关程序功能的问题时,面向对象的主题优于程序性主题。这表明函数信息在其程序的心理表示中更容易获得,并支持这样一种论点,即面向对象的符号突出显示了单个类级别的函数。对于较长的程序,未找到相应的效果。在所有类型的问题上,过程主题的理解均优于面向对象的主题。面向对象的主体在较大的程序中回答问题时遇到的困难表明,他们在整理信息并从中推论时面临问题。我们建议,该结果可能与面向对象样式的新手更长的学习曲线有关,还与OO样式的特性和特定的OO语言符号有关。

也可以看看:


1
我尊重实验研究。然而,存在确定它们解决正确问题的问题。所谓的OOP以及使用它的方式中有太多变量,单个研究有意义,恕我直言。像编程中的许多事情一样,OOP由专家创建以满足他们自己的需求。在讨论OOP的有用性时(我没有将其作为OP的主题,而是它是否解决了程序化编程的缺点),人们可能会问:什么功能,为谁而设计,目的是什么?只有这样,实地研究才变得完全有意义。
2014年

1
轶事警告:如果问题很小(例如,最多约500-1000行代码),那么OOP对我的经验没有影响,甚至可能由于不得不担心差别不大的东西而受到阻碍。如果问题很大,并且具有某种形式的“可互换件”,那么以后必须要在组织 OOP学科提供的组织(GUI中的窗口,操作系统中的设备...)上进行添加。您当然可以在没有语言支持的情况下对OOP进行编程(例如,参见Linux内核)。
vonbrand 2015年

1

小心。阅读R. King的经典著作《我的猫是面向对象的》(《面向对象的概念,数据库和应用程序》(Kim和Lochovsky编辑)(ACM,1989)。“面向对象”已经成为一个时髦的词汇,而不是一个清晰的概念。

此外,主题有很多变体,几乎没有共同点。有基于原型的语言(继承是来自对象,没有这样的类)和基于类的语言。有些语言允许多重继承,而其他则不允许。一些语言的想法类似于Java的接口(可以看作是减少多重继承的一种形式)。有混合的想法。继承可能相当严格(就像在C ++中一样,不能真正改变子类中的内容),也可以非常自由地处理(在Perl中,子类几乎可以重新定义所有内容)。一些语言具有单个继承根(通常称为Object,具有默认行为),另一些则允许程序员创建多个树。一些语言坚持认为“一切都是对象”,另一些则处理对象和非对象,有些(例如Java)具有“大多数是对象,但这里的少数几种不是”。一些语言坚持在对象中严格封装状态,另一些语言则使其成为可选对象(C ++的私有,受保护,公共),而其他语言则根本没有封装。如果从正确的角度斜视诸如Scheme之类的语言,您会发现内置的OOP无需任何特殊的努力(可以定义返回封装某些局部状态的函数的函数)。


0

简而言之,面向对象编程解决了过程编程中存在的数据安全问题。这是通过使用封装数据的概念完成的,仅允许合法类继承数据。访问修饰符有助于实现此目标。希望能有所帮助。


过程编程中存在哪些数据安全问题?
user31782 2014年

在过程编程中,不能限制全局变量的使用。任何函数都可以使用它的值。但是,在OOP中,我可以将变量的使用范围限制为仅某个类,或者仅限于继承该类的类。
manu 2014年

在过程编程中,我们也可以通过将变量用于某些函数来限制全局变量的使用,即不声明任何全局数据。
user31782 2014年

如果不全局声明它,则它不是全局变量。
马努

1
没有说明,“安全”或“正确”并不意味着任何东西。这些都是为了实现针对该目标的代码规范的尝试:类型,类定义,DesignByContract等。您可以在使私有数据边界不可侵犯的意义上获得“安全性”;假设您必须遵守虚拟机指令集才能执行。对象定向不会向可以直接读取内存的人隐藏内部内存,并且糟糕的对象协议设计正在按设计分发秘密。
罗布
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.