OOP 是否实现了代码重用的承诺?有哪些替代方案可以实现代码重用?
https://softwareengineering.stackexchange.com/questions/7618
-
16-10-2019 - |
题
也许使用面向对象范例的最大希望是代码重用。一些人对这一目标的实现存在争议。为什么它(没有)实现?
正如 OOP 定义的那样,代码重用是否可以提高项目的生产力?
或者更容易管理?或者更容易维护?还是质量更高?
可能我们都同意代码重用是一件好事,但是有多种方法可以实现这一目标。问题是关于OOP提供的代码重用方法。这是一件好事吗? 有没有更好的方法来实现代码重用 比面向对象、子类、多态性等?有哪些方法比较好呢? 为什么?
告诉我们您在 OOP 重用或其他范例重用方面的经验。
解决方案
代码重用是一个很好的主意。 不是一个好人.
我有一个大约30年的软件工程的视角,试图“重复使用”。
在80年代,我开始研究“代码重用”作为研究主题,因为发现我已经重新设计了我在70年代初建造的一个操作系统的设计,这是我在70年代后期建造的另一个操作系统。
代码重复使用的好部分是有时能够重用诚实到神的固定代码。但是世界是 满的 代码;如何找到想要的东西?这就是我所说的 重复使用诅咒:
我是圣诞老人(OK开源),我有一袋10亿个软件组件。您可以拥有其中的任何一个。
祝你好运。
很好地解决重用问题:
- Reuser需要以某种方式指定他的需求(功能性,性能,目标语言,环境假设,...)
- 必须有一个“可重复使用”代码的库,这些代码已通过这些潜在标准以各种方式索引
- 必须存在一些机制来挑选候选元素(在十亿个元素中,您不能亲自查看它们)
- 需要有一种表征来表征距选择的候选人的规格多远的方法
- 应该存在一些常规过程,以允许Reuser修改所选的可重复使用的代码(这是OOP的最大贡献:您可以通过覆盖其插槽来编辑现有组件/对象。OOP不提供任何其他帮助)。
- 所有这些都必须显然比简单地重新编码便宜
多年来发现的主要内容是,要重复使用代码,它必须是为此目的而设计的,或者包含太多隐含的假设。最成功的代码重用库实际上很小。可以说,库和框架是“可重复使用的”代码,它们非常成功。 Java和C#成功不是因为它们是很好的计算机语言,而是因为它们具有巨大的精心设计,实施和记录的库。但是人们不看图书馆中的源代码。他们只是称为有据可查的API(通常是可用的)。
什么代码重复使用尚未完成(都不是)提供了我们编码系统能力的数量级提高订单。
我认为关键缺陷是 任何类型的代码重用在根本上都是有限的,因为代码内置了太多的假设. 。如果您使代码很小,则可以最大程度地减少假设,但是从头开始构建的成本并不是很大,并且重复使用的收益无效。如果您使代码块巨大,那么它们在新的背景下几乎没有用。像格列佛一样,它们被一百万个小弦绑在海滩上,而您根本就不能将它们全部割伤。
我们应该做的是 重用知识来构建代码. 。如果我们可以做到这一点,那么我们可以将这些知识应用于构造所需的代码,并处理当前的假设集。
为此,仍然需要相同的规范功能来表征软件组件(您仍然必须说出想要的内容!)。但是然后,您将此“构造”知识应用于规格 产生 您想要的代码。
作为一个社区,我们还不是很好。但是人们一直这样做。我们为什么不能自动化它?有很多研究,这表明在许多情况下可以做到这一点。
这需要的一个关键机器是接受“组件描述”的机械工具(这些只是正式文档,可以像编程语言一样解析)并应用 程序转换 给他们。
编译器已经这样做了: - },他们确实擅长解决问题的类别。
具有代码生成的UML模型是一种尝试。不是一个很好的尝试;在大多数UML模型中,几乎都说的是“我有看起来像这样的数据”。如果遗漏了功能,很难生成真实程序。
我正在尝试建造 实用程序转换系统,一种称为DMS的工具. 。通过将程序转换应用于抽象规范来生成代码,而是将其清理为清理代码,而不是将程序转换应用于抽象规范,这使得非常分散注意力。 (在抽象中这些问题是相同的问题!)。 (要构建这样的工具需要很多时间;我已经这样做了15年,而与此同时您必须吃饭)。
但是DMS具有我上面描述的两个关键属性:处理任意形式规格的能力,以及捕获“代码生成知识”作为转换的能力,并按需应用它们。值得注意的是,我们确实在某些特殊情况下产生了一些有趣的代码。 DM在很大程度上是利用自己来生成其实现的。这为我们实现了至少(知识)重用的某些承诺:生产力极为重要。我有一个大约7个技术人员的团队;我们已经为DMS编写了1-2个“规格”的MSLOC,但有大约10msloc的生成代码。
概括: 再利用一代知识 是胜利,不是 重复使用代码.
其他提示
代码重复使用是在OOP中实现的,但在功能编程中也可以实现。每当您取一个代码块并通过其余代码使其可调用,以便您可以在其他地方使用此功能是代码重复使用。
这种类型的代码重复使用也使代码更易于管理,因为更改此可调用块更改了所谓的所有位置。我会说这一结果也提高了质量和可读性。
我不确定OOP是否只是在那里提供代码重复使用。我将OOP视为与对象交互并抽象数据结构细节的一种方法。
面向对象的编程的根源可以追溯到1960年代。随着硬件和软件变得越来越复杂,可管理性通常成为一个问题。研究人员研究了维持软件质量并开发面向对象的编程的方法,部分原因是通过强烈强调离散的,可重复使用的编程逻辑单元[需要引用]。该技术专注于数据而不是过程,其中包含由自给自足的模块组成的程序(“类”),每个实例(“对象”)都包含操纵自己的数据结构(“成员”)所需的所有信息。这与多年来一直占主导地位的现有模块化编程相反,该编程重点是模块的功能,而不是专门用于数据,但同样提供了代码重复使用,并且可以自给自足的编程逻辑单元,从而使协作能够协作通过使用链接模块(子例程)。这种仍然持续存在的更传统的方法倾向于分别考虑数据和行为。
是的,否
代码重复使用是许多不同活动的全部术语。
- 在一个项目中重复使用代码。 OO非常适合此,设计良好的应用程序将密切绘制模型世界的关系,从而尽可能多地消除重复的代码。但是,您可以说Pre-OO技术可以实现同一件事,这是事实,但是OO在许多方面都更加方便。
- 第三方图书馆 无论有没有OO,这似乎都可以很好地工作。
- 交叉用途代码重复使用 OO的最大代码挽回承诺是,该代码曾经为一个应用程序编写,以后可以重复使用另一个应用程序,这并非专门为其设计。当OO的概念通过高级管理办公室的大门过滤时,这真是愤怒,OO完全未能实现它。事实证明,目的是OO设计的关键方面(可能是所有程序代码,但这只是我的理论),并尝试重新利用代码以维护灾难结束。 (一个著名的对旧框架的反图案,没有人敢于修改,其朋友,略有不同的for-for-ever-ever-ever-app通常是从这里开始的)。
我会发布一个很长的答案,但为什么呢?乌迪·达汉(Udi Dahan)对此的解释比我好得多。
http://www.udidahan.com/2009/06/07/the-fallacy-of-reuse/
这是帖子的开头:
这个行业与重复使用相处。
有一种信念是,如果我们重复更多的代码,一切都会更好。
有些人甚至说,对象取向的全部要点是重复使用的 - 事实并非如此,封装是最大的。在该组件 - 方向是应该使重复使用的事情。显然,这也没有那么好,因为我们现在在这里将希望的希望固定在服务方向上。
关于如何通过一天的方向来实现重新使用的全部图案书籍。从实体服务和活动服务,通过过程服务和编排服务,服务已被归类为尝试实现这一目标的各种方式。编写服务已被吹捧为重复使用和创建可重复使用的服务的关键。
我不妨让您进入肮脏的小秘密:
重用是一个谬论
我同意克里斯的观点,功能编程是重复使用代码的好方法。
许多程序具有重复出现的代码结构。为此 设计模式 在OOP世界中使用,但这可以通过 递归功能 和 模式匹配 在功能编程语言中。有关此的更多信息,请参见 现实世界功能编程.
我认为,在许多情况下,OOP中的深层继承可能会产生误导。您有一个类,许多密切相关的方法在不同的文件中实现。作为 乔·阿姆斯特朗 关于OOP说:
面向对象的语言的问题在于他们拥有随身携带的所有隐性环境。您想要一个香蕉,但您得到的是一颗拿着香蕉和整个丛林的大猩猩。
高阶功能 当涉及代码重复使用时,也非常有用 map
和 foldr
那是Google的基础 MapReduce.
异步消息传递 也是组织复杂软件的好方法,一些计算机科学家指出,假定对象与彼此的异步进行通信 告诉,不要问 原则。在此中查看有关此的更多信息 面向对象的编程:错误的路径? 是 乔·阿姆斯特朗 引用:
我开始想知道什么是面向对象的编程,我认为Erlang不是面向对象的,这是一种功能性编程语言。然后,我的论文主管说:“但是您错了,Erlang非常面向对象”。他说,面向对象的语言不是面向对象的。我可能认为,尽管我不太确定我是否相信这一点,但是Erlang可能是唯一面向对象的语言,因为以对象为导向的编程的3个原则是基于 消息传递, ,你有 隔离 在对象之间 多态性.
与事件驱动的系统和在Erlang中一样,异步消息传递也是解脱系统和 松散的耦合 在复杂的系统中很重要。借助足够的解耦系统,您可以在运行时发展该系统,也许可以在不同的节点上发展。 Unibet对此做出了很好的演讲: 域活动驱动建筑
但是,我认为大多数代码重用都是通过使用库和框架来完成的。
不起眼的Unix管为代码重用所做的比其他任何事情都要多。当对象出现时,恰好是一种直观的构造代码方式,后来人们开始对任何东西进行探讨。在一般的对象中是用于封装,而不是代码重用,代码重复使用需要更多的东西,并且类继承层次结构是代码重用机制的真正替代方法。
OOP并不特别;您可以使用或不使用OOP制作可重复使用的代码。 纯粹的功能特别可重复使用: : 例如, java.lang.math.sqrt(double)
输入一个数字并给出一个数字。没有OOP,但绝对比大多数代码重复使用。
从功能编程视图中,OOP主要是关于管理状态。
在功能编程中,您可以轻松拥有数百个有用的功能: http://haskell.org/ghc/docs/6.12.1/html/libraries/base-4.2.0.0/data-list.html.
您会在列表级中有数百种方法吗?公共方法被认为是要保持小的内部状态的接口。
可悲的是,有些人没有使用许多小功能,而是重复功能。对我来说是因为OOP 不鼓励代码重用 与功能编程一样多。
对我来说,是的,但并非一直以来,而且本可以做其他方法。
在大多数情况下,通过创建抽象基类并创建该类别的具体实现。
同样,许多框架都利用继承来提供代码重复使用(Delphi,Java,.net只是立即浮现的一些框架)。
这并不是说很多公用事业库和代码片段也无法完成这项工作,但是设计精良的对象层次结构令人愉悦。
根据我的经验,与使用继承层次结构之类的OOP原则相比,通过通用编程设施(例如C ++模板)利用“可重复使用”代码的成功更大。
OOP太开放了,无法有效重复使用。
有太多重复使用的方法。每个公共课程都问: “做一个新实例!”, ,每种公共方法都说: “打电话给我!”, ,每个受保护的方法产生: “覆盖我!” - 所有这些重复使用方式都是 不同的, ,它们具有不同的参数,它们出现在不同的上下文中,都有不同的规则,如何调用/扩展/覆盖它。
API 更好的是,这是OOP(或非木板)点的严格子集,但是在现实生活中,API过度效力和永远成长,连接点仍然太多。此外,良好的API可以使生活更轻松,这是为OOP提供接口的最佳方法。
Datadlow范式 为组件提供严格的接口,它们具有以下类型的端口:
- 消费者(输入),
- 生产者(输出)。
取决于域,有一些数据包类型,因此消费者和生产者可以将其连接起来,它们具有相同的(或兼容)端口。它最美丽的部分可以在视觉上完成,因为没有参数或连接的orher调整,它们实际上只是连接消费者和生产者。
我有点不清楚,您可能会看看 stackoverflow上的“ dataflow”标签, , 或者 Wikipedia“ DataFow编程” 或Wikipedia “基于流程的编程”.
(此外,我已经在C ++中编写了一个数据流系统,因此OOP和DF不是敌人,DF是一种更高级别的组织方式。)
在 CommonLisp 中,有很多方法可以实现重用:
动态类型,让你的代码默认是通用的
命令式抽象,即子程序
面向对象,具有多重继承 和 多次调度
语法抽象,定义新语法结构或缩写样板代码的能力
函数抽象、闭包和高阶函数
如果您尝试将 CommonLisp 体验与其他语言进行比较,您会发现简化代码重用的主要功能是存在 两个都 面向对象和功能抽象。它们比替代方案更具互补性:如果没有其中之一,您将被迫以一种笨拙的方式重新实现缺失的功能。例如,请参阅用作闭包和模式匹配的函子类来获取不可扩展的方法分派。
确实没有人们描述它的方式“重复使用”。重用是一个 偶然 任何事物的财产。很难计划。当大多数人谈论“重用”时的含义是“使用”。这是一个诱人和令人兴奋的术语。当您使用库时,通常将其用于原本打算。你是 不是 重复使用它,除非您正在做真正疯狂的事情。
从这个意义上讲,现实世界中的再利用是关于重新修复事物。我可以在这里重复使用这些座位,然后重新布置它们形成...床!不是很舒适的床,但我可以做到。那不是他们的主要用途。我正在将它们重复出现在其最初的适用性领域之外。 [...]明天,我将飞回英国。我会 不是 重复使用飞机。我只能将其用于预期的目的,这对此没有任何奇特或令人兴奋的方式。
- 凯夫林·亨尼(Kevlin Henney)
我将冒着嘲笑和坦白的风险,我只是最近才使用OOP。它不会自动出现。我的大部分经验都涉及关系数据库,因此我认为在桌子和加入中。有声称最好从一开始就学习它,这避免了编程时必须重新连接您的思维。我没有那种奢侈,拒绝在某种象牙塔理论上将我的职业生涯报废。像其他一切一样,我会弄清楚。
起初,我认为整个概念没有意义。这似乎是不必要的,太多了。我知道,这是疯狂的谈话。显然,在您欣赏任何事物的好处或以更好的方法为基础之前,它需要一定的理解。
代码重用需要不重复代码,了解如何完成代码,预先计划。当您决定有不值得的情况下,您是否应该避免重复使用代码?而且,没有任何语言如此严格,以至于当它认为您应该从另一类继承代码时会出现错误。充其量,它们提供了有利于实施的环境。
我认为OOP的最大好处是对应如何组织代码的普遍接受。其他一切都是肉汁。一个程序员团队可能无法就所有课程的构建方式完全达成共识,但是他们应该能够找到代码。
我已经看到足够的程序代码知道它可能在任何地方,有时甚至到处都是。
哦,给你 更多的 重复使用代码的方法。就这些。
水平再利用:方面、特征、嫁接
经典的面向对象有时在代码重用方面存在不足,特别是当您因缺乏更好的方法在类之间共享实际功能而对所有继承感到疯狂时。针对这个问题,创建了横向复用机制,如AOP、traits、grafts等。
面向方面的编程
我认为 AOP 是 OOP 中缺失的一半。AOP 并不是真正为人所知,但它已经进入了生产代码。
我将尝试用简单的术语来解释:想象一下,您可以使用称为方面的特殊结构来注入和过滤功能,这些方面具有“方法”,可以定义通过以下方式影响什么以及如何影响 反射, ,但是在编译时,这个过程被称为 编织.
一个示例是一个方面,它告诉“对于某些以 get 开头的类的所有方法,您的程序会将获取的数据及其获取时间写入日志文件”。
如果你想更好地理解 AOP,请观看这两场演讲:
- 使用面向方面的编程来防止应用程序攻击 1/6
- 面向方面的编程:模块化的激进研究 经过 格雷戈尔·基克萨莱斯 (AOP背后的主要人物)
性状与嫁接
性状 是定义可重用代码以补充 OOP 的另一种构造,它们类似于 混入, ,但更干净。
不是解释它们,而是 一个很棒的 PHP RFC 解释了两者. 。顺便说一句,特性即将进入 PHP,它们已经被提交到主干。
总之
在我看来,正如我们今天所熟知的那样,OOP 仍然是模块化的关键 OOP 仍然不完整.
OOP提供了一组有用的工具,可让您编写可以在没有这些工具的情况下使用的代码。如果你写一个 PrintIt
接收任何旧对象并调用的功能 .toString()
在它上,您将使用多种对象称呼该代码后立即重复使用该代码。使用这些工具, 每行代码都做得更多。
现在,赶时髦的人在时髦人士中非常热。它为您提供 整个分开 制作每行代码的一组工具会做更多。它可能不是更好的或有效的,但是在工具箱中提供了另一个工具。
(有一个疯狂的主意,要有一个额外的面向对象的重复使用:这个想法是我们可以定义一个 Customer
上课并在我们编写的每个应用程序中使用它。然后,应用程序在这里和那里只会有些胶水。这不起作用。但这并不意味着OO失败,甚至重复使用失败。在应用程序中重新使用代码的基本类型使得编写功能更多并更快地编写的应用程序。)
阅读上述帖子,几句话:
- 许多人认为,OOP中的代码重复使用意味着继承。我不同意。接口和合同是OOP系统中代码重复使用的核心。 OOP是创建组件技术的灰色框尝试。
- 特定于域和通用“框架”作为重复使用的主题之间的差异认为我太抽象了。在我看来,只有在理解它的问题时,才能完成一个组件(简洁,最小和可重复使用的接口合同以及背后的实现)。允许非域专家以较少了解域的工作是一个(重新)有用的组件的特定组件。用户需要理解界面,少于问题域的复杂性。
- 重复使用的级别经常被遗忘:重复使用,规格重复使用,体系结构/设计重复使用,接口重复使用,测试案例重复使用。代码重复使用并不总是有利的。但是,通常要坚持特定的体系结构来解决一种新的类似产品,这是一个很大的节省时间。
- 我眼中的OOP设计模式(Gamma et al)详细阐述了战术实施技术,而不是在更大范围内重新使用代码的背景下具有有意义的意义。他们有助于用OOP元素编写应用程序,但我不会将它们视为对“代码重复使用”问题的解决方案,而不是单个应用程序。
- 也许这不公平:C/C ++/C#经验和6个月功能编程(F#)的20年。启用重复使用的一个主要要素是:人们需要轻松找到“接口”,研究,理解它,然后使用它。纯粹的功能编程并不能使我很容易看到结构,重复使用的候选者或一切开始的地方以及一切结束的地方。如此受赞扬的“句法糖”通常是我眼中的盐,使我无法轻易看到发生了什么。因此,我不太可能尝试重新使用功能(这是什么 - 一堆功能?),哪些可能没有我什至看不到的隐藏副作用(懒惰的评估,monads,...)。不要误会我的意思,功能性编程的方面非常酷,但是我宣称的所有优势都充满了疑问。我非常好奇,后功能后的未来带来了什么,并希望在我退休之前看到它;)
- 规格,设计,实现是耦合的,但在“同一事物”上不容易遍历视图。与新的编程范式相比,要缩小差距,提高(自动推理,可追溯性)这些观点之间的相互利益更为重要。形式化规范语言,标准化测试符号(例如TTCN3)和编程语言支持验证界面和合同针对规格而无需评论的合同,这可能是我们最紧急需要的。
这个问题是更微妙的恕我直言:
- OOP是一个 用可变状态构造代码的绝佳方法. 。通过将状态封装到对象中,命令式状态代码变得更易于理解 至少这种特定状态只能通过此类方法修改。 (顺便说一句,您可以轻松地通过滥用继承来破坏这一收益。)这已经足够了,但是 更好 甚至没有这个。
- 具有可变状态的代码本质上很难重复使用. 。使用不变的数据结构比代码要难。
所以 从POV制作可重复使用的代码的POV本身并不糟糕, , 但 使用OOP编写的代码种类本质上很难重复使用.
还, 功能编程 可能会导致 更多可重复使用的代码. 。但是,在满足截止日期的同时,让抽象正确编写理智的功能代码可能是不可能的。 “半右”抽象将更容易表达OOP样式。而且它不会倾向于 更容易重复使用代码 - 较高水平的抽象意味着代码理解将需要程序员有限的认知能力进行更高的前期投资。
作为一个实际的例子: 游戏代码涉及许多可变状态,因为这是考虑编码游戏的自然方式,除非它是一种非常困惑/算法,因此显然它最终是使用OO结构的。当然很难重复使用。但是相同的代码,包含相同的知识, 没有OOP,将很难重复使用. 。然后将其重写为功能风格可能需要 完全改变您考虑该代码的方式以及其背后的知识。 是的,由此产生的 代码背后的知识 OO之后会更清楚地重写FP ...但是 费用可能很大 可能是 希望重复使用您最终得到的令人难以置信的智能和抽象的代码的人也必须支付的成本, ,因此自相矛盾的是,即使从技术上讲,人们最终都不会重复使用该代码。
...导致最后一个微妙:代码重复使用是关于 人|代码 接口,不仅仅是代码。 OOP在服务此界面方面做得不错,因为它可以很好地映射到当今编写的多种代码。 FP可能更好地用于代码重复使用,但恕我直言不讳 轻松地重复使用人们实际需要编写的那种代码。 这将改变为我们需要编写更改的代码类型。
PS,如果有人想说“ OO不是关于可变状态的,您也可以拥有带有不可变的状态的OO” ...我称之为“使用类作为名称空间的FP”。当它适用于您时,它很棒,并且避免了某些语言模块系统的某些缺点,并且可能导致更可重复使用的代码。但这不是一个oo;)