关于C ++和面向对象的编程

哈Ha!



我们想引起您对这篇文章的注意,该文章的作者在使用C ++语言时不赞成使用纯粹的面向对象的方法。如果可能,我们请您不仅评估作者的论点,而且要评估其逻辑和风格。



目前已经有很多的写作最近关于C ++和地方语言的发展方向和多少的所谓的“现代C ++”,根本就不是为游戏开发者的选择。



尽管我完全同意这种观点,但我倾向于将C ++的演变视为大多数开发人员所遵循的根深蒂固的思想的结果。在本文中,我将尝试结合自己的想法来组织其中的一些想法-也许我会得到一些启发。



面向对象编程(OOP)作为工具



尽管C ++被描述为一种多范式编程语言,但实际上,大多数程序员只是将C ++纯粹用作一种面向对象的语言(通用编程用于“补充” OOP)。



OOP被认为是一种工具,程序员可以用来解决代码问题的许多范例之一。但是,以我的经验,OOP被大多数专业人士接受为软件开发的黄金标准。基本上,开发解决方案首先要确定我们需要哪些对象。在特定对象之间分配了代码之后,才开始解决特定问题。随着这种面向对象思维的转变,OOP从一种工具变成了一个完整的工具箱。



熵是推动软件开发的秘密力量



我喜欢将OOP解决方案视为一个星座:这是一组对象,它们之间具有随机绘制的线条。这样的解决方案也可以看作是一个图,其中对象是节点,它们之间的关系是边,但是星座隐喻传达的一个群/簇的现象离我更近了(相比之下,该图太抽象了)。



但我不喜欢这种“物体群”是如何构成的。以我的理解,每个这样的构图无非是程序员脑海中形成的图像快照,反映了特定时刻解决方案空间的样子。即使考虑到面向对象设计中关于可扩展性,可重用性,封装性等方面的所有承诺,未来也是无法预料的,​​因此在每种情况下,我们都可以为当前面临的任务提供解决方案。



我们应该受到鼓舞,认为我们“只是”解决了摆在我们面前的问题,但是根据我的经验,以OOP精神使用设计原则的程序员可以创建一个解决方案,同时以假设问题本身不会显着改变的方式约束自己,并且,因此,该解决方案可以被视为永久解决方案。我的意思是,从现在开始,人们开始谈论构成上述星座的对象而不是数据和算法方面的解决方案。问题本身是抽象的。

尽管如此,该程序至少在其他任何系统上都具有熵,因此,我们都知道代码会发生变化。而且,以一种不可预测的方式。但是对于我来说,在这种情况下,绝对清楚的是,如果您不自觉地与之抗争,那么代码无论如何都会降级,陷入混乱和混乱中。



我已经在OOP解决方案中以许多不同的方式看到了这一点:



  • 新的中间级别出现在层次结构中,而最初并不打算引入它们。
  • 在大多数层次结构中,使用空实现添加了新的虚函数。
  • 星座中的一个对象比计划的需要更多的处理,因此,其他对象之间的连接开始滑动。
  • , , , .
  • .…


这些都是组织扩展性不当的示例。而且,结果总是相同的,可能会在几个月甚至几年后出现。在重构的帮助下他们试图消除对OOP设计原则的违反,这些原则是在将新对象添加到星座图时又由于问题本身的重新制定而添加的。有时重构会有所帮助。一阵子。熵是稳定的,程序员没有时间重构每个OOP星座图来克服它,因此任何项目都经常发现自己处于相同的情况下,即混乱。



在任何OOP项目的生命周期中,迟早都会出现一个点,在那之后无法维护它。通常,在这一点上,您应该执行以下两项操作之一:



  • « »: - . , , , , , .
  • : -, , , .


请注意:如果必须继续开发新功能和/或仍然需要消除错误,带有黑框的选项仍将需要重写。



重写解决方案的情况使我们回到了特定时刻可用解决方案空间快照的现象。那么在OOP设计1和当前情况之间发生了什么变化?基本上就是这样。问题已更改,因此需要其他解决方案。



在编写解决方案时,遵循OOP设计原则,我们对问题进行了抽象,并且一旦问题改变,我们的解决方案便像纸牌屋一样崩溃了。

我认为是在这一刻,我们开始怀疑出了什么问题,我们尝试另辟way径,并根据事后分析(汇报)的结果更新解决问题的策略。但是,每次遇到这样的“重写时间”场景时,都不会改变:再次使用OOP原则,根据新原则,根据问题空间的当前状态实施新快照。重复整个循环。



易于将代码删除作为设计原则



在基于OOP原理构建的任何系统中,“星座”中的对象都是最受关注的对象。但是我相信对象之间的关系甚至比对象本身更重要。



我更喜欢简单的解决方案,其中代码的依赖图由最少数量的节点和边组成。解决方案越简单,不仅要更改它,而且要删除它也越容易。我还发现删除代码越容易,您就可以更快地重新定向解决方案并使之适应不断变化的问题条件。同时,代码变得对熵更具抵抗力,因为它花费了更少的精力来使其保持有序并防止其陷入混乱。



关于性能定义



但是,避免OOP设计的主要考虑因素之一是性能。您需要运行的代码越多,性能就会越差。



也不可能没有注意到,按照定义,OOP功能并不能提高性能。我已经实现了带有接口和两个派生类的简单OOP层次结构,这些派生类重写了Compiler Explorer中的单个纯虚函数调用



此示例中的代码将打印“ Hello,World!”,或者不打印“ Hello,World!”,具体取决于传递给程序的参数数量。除了直接编程我刚才描述的所有内容之外,标准的OOP设计模式之一(继承)将用于解决代码中的此问题。



在这种情况下,最引人注目的是即使经过优化,编译器也会生成多少代码。然后,仔细查看,您会发现这种维护是多么昂贵,同时又无用:当将非零数量的参数传递给程序时,代码仍会分配内存(call new),加载vtable两个对象的地址,Work()为其加载函数的地址ImplB并跳转到该地址,然后立即返回,因为那里无事可做。最后,调用它delete以释放分配的内存。



这些操作根本没有必要,但是处理器可以正确执行所有操作。



因此,如果产品的主要目标之一是实现高性能(否则会很奇怪),则在代码中应避免不必要的昂贵操作,而应选择易于判断的简单操作,并使用有助于实现此目标的结构。



Unity为例。由于近期实践的一部分,性能是正确使用C#,面向对象的语言,因为这种语言在发动机本身就已经使用。但是,他们基于C#的一个子集,而不是与OOP严格相关子集,并且在此基础上,他们创建了为提高性能而锐化的构造。



鉴于程序员的工作是使用计算机来解决问题,所以我们的业务很少专注于编写实际上使处理器完成处理器特别擅长的工作的代码,这是不可想象的。



关于打击定型观念



Angelo Pesce文章过度复杂化是万恶之源”中,作者承认大多数软件问题实际上都是人为因素,从而大获全胜(请参阅最后一部分:人)。



团队中的人员需要进行互动,并对总体目标是什么以及实现目标的途径形成共识。例如,如果团队在达成目标的途径上存在分歧,则为了取得进一步的进展,有必要达成共识。如果意见分歧很小,通常这并不难,但是如果选项之间存在根本性差异(例如“ OOP或非OOP”),则容忍得多得多。

改变主意并不容易。怀疑您的观点,意识到自己的错误程度并调整自己的过程是艰难而痛苦的。但是改变别人的想法要困难得多!



关于OOP及其内在问题,我已经与很多人进行了很多对话,尽管我相信我一直能够解释为什么我以这种方式而不是其他方式来解释,但我认为我没有设法使任何人摆脱OOP。



没错,在这些年的工作中,我为自己确定了三个主要论点,因此人们不准备给对方机会:



  • « ». « ». « » . , , ( , - ). « …».
  • « , , , ». «» , , . , « ».
  • “每个人都知道OOP,与具有共同知识的通用语言的人交谈非常方便。” 这是一个逻辑上的错误,称为“对人的争论”,也就是说,如果几乎所有程序员都使用OOP原理,那么这种想法就不会不合适。


我完全意识到,揭露论证中的逻辑错误不足以揭穿它们。但是,我相信看到自己的判断中的缺陷,您就可以找到真相的深处,并找到拒绝不寻常想法的深层原因。



All Articles