据我了解,OOP 是大型项目最常用的范例。 我还知道大型系统的一些较小的子集使用其他范例(例如 SQL,它是声明性的),并且我还意识到在较低的计算级别 OOP 并不真正可行。 但在我看来,通常高层解决方案的各个部分几乎总是以 OOP 方式组合在一起。
是否存在任何场景,真正的非 OOP 范式实际上是大规模解决方案的更好选择? 或者说这在当今是闻所未闻的?
自从我开始学习CS以来我就一直想知道这个问题; 人们很容易感觉到 OOP 是一种永远无法被超越的编程必杀技。
From what I understand, OOP is the most commonly used paradigm for large scale projects. I also know that some smaller subsets of big systems use other paradigms (e.g. SQL, which is declarative), and I also realize that at lower levels of computing OOP isn't really feasible. But it seems to me that usually the pieces of higher level solutions are almost always put together in a OOP fashion.
Are there any scenarios where a truly non-OOP paradigm is actually a better choice for a largescale solution? Or is that unheard of these days?
I've wondered this ever since I've started studying CS; it's easy to get the feeling that OOP is some nirvana of programming that will never be surpassed.
发布评论
评论(12)
在我看来,OOP 被如此广泛使用的原因并不是因为它是适合这项工作的工具。 我认为更重要的是能够以客户理解的方式向客户描述解决方案。
汽车是具有发动机的车辆。 这就是编程和现实世界合二为一!
很难理解任何可以如此优雅地适应编程和现实世界的东西。
In my opinion, the reason OOP is used so widely isn't so much that it's the right tool for the job. I think it's more that a solution can be described to the customer in a way that they understand.
A CAR is a VEHICLE that has an ENGINE. That's programming and real world all in one!
It's hard to comprehend anything that can fit the programming and real world quite so elegantly.
Linux 是一个大型项目,很大程度上不是 OOP。 而且它也不会从中获得太多好处。
我认为 OOP 具有良好的吸引力,因为它与良好的编程实践(如封装、数据隐藏、代码重用、模块化等)相关联。 但这些优点绝不是面向对象编程所独有的。
Linux is a large-scale project that's very much not OOP. And it wouldn't have a lot to gain from it either.
I think OOP has a good ring to it, because it has associated itself with good programming practices like encapsulation, data hiding, code reuse, modularity et.c. But these virtues are by no means unique to OOP.
您可能会看一下 Joe Armstrong 编写的 Erlang。
维基百科:
乔·阿姆斯特朗:
You might have a look at Erlang, written by Joe Armstrong.
Wikipedia:
Joe Armstrong:
OOP 的承诺是代码重用和更容易维护。 我不确定它是否已交付。 我认为 dot net 之类的东西与我们过去从各个供应商那里获得的 C 库非常相似。 如果需要,您可以将该代码称为重用。 至于维护,坏代码就是坏代码。 OOP 没有帮助。
The promise of OOP was code reuse and easier maintenance. I am not sure it delivered. I see things such as dot net as being much the same as the C libraries we used to get fro various vendors. You can call that code reuse if you want. As for maintenance bad code is bad code. OOP did not help.
我是 OOP 的忠实粉丝,我每天都练习 OOP。
这是编写代码的最自然的方式,因为它类似于现实生活。
不过,我意识到 OOP 的虚拟化可能会导致性能问题。
当然,这取决于您的设计、您选择的语言和平台(使用基于垃圾收集的语言(例如 Java 或 C#)编写的系统可能比使用 C++ 编写的系统性能更差)。
我想在实时系统中,过程式编程可能更合适。
I'm the biggest fan of OOP, and I practice OOP every day.
It's the most natural way to write code, because it resembles the real life.
Though, I realize that the OOP's virtualization might cause performance issues.
Of course that depends on your design, the language and the platform you chose (systems written in Garbage collection based languages such as Java or C# might perform worse than systems which were written in C++ for example).
I guess in Real-time systems, procedural programming may be more appropriate.
请注意,并非所有声称是 OOP 的项目实际上都是 OOP。 有时,大部分代码都是过程性的,或者数据模型贫血,等等。 ..
Note that not all projects that claim to be OOP are in fact OOP. Sometimes the majority of the code is procedural, or the data model is anemic, and so on...
Zyx,你写道,“大多数系统都使用关系数据库......”
恐怕没有这样的事情。 关系模型明年就40岁了,但至今仍未实现。 我想你的意思是“SQL 数据库”。 您应该阅读 Fabian Pascal 的任何文章,以了解关系 dbms 和 SQL dbms 之间的区别。
“......通常选择关系模型是因为它很受欢迎,”
确实,它很受欢迎。
“……工具的可用性”,
可惜没有必要的主要工具:关系模型的实现。
“支持”,
是的,我确信关系模型有很好的支持,但 dbms 实现完全不支持它。
“事实上,关系模型实际上是一个数学概念,”
是的,这是一个数学概念,但是,由于没有被实现,它很大程度上局限于象牙塔。 弦理论也是一个数学概念,但我不会用它来实现一个系统。
事实上,尽管它是一个数学概念,但它肯定不是一门科学(如计算机科学),因为它缺乏任何科学的第一个要求:它是可证伪的:没有关系数据库管理系统的实现,我们可以根据它来检查其索赔。
这是纯蛇油。
“……与面向对象编程相反。”
与 OOP 相反,关系模型从未被实现过。
购买一本有关 SQL 的书并提高工作效率。
将关系模型留给效率低下的理论家。
Zyx, you wrote, "Most of the systems use relational databases ..."
I'm afraid there's no such thing. The relational model will be 40 years old next year and has still never been implemented. I think you mean, "SQL databases." You should read anything by Fabian Pascal to understand the difference between a relational dbms and an SQL dbms.
" ... the relational model is usually chosen due to its popularity,"
True, it's popular.
" ... availability of tools,"
Alas without the main tool necessary: an implementation of the relational model.
" support,"
Yup, the relational model has fine support, I'm sure, but it's entirely unsupported by a dbms implementation.
" and the fact that the relational model is in fact a mathematical concept,"
Yes, it's a mathematical concept, but, not being implemented, it's largely restricted to the ivory towers. String theory is also a mathematical concept but I wouldn't implement a system with it.
In fact, despite it's being a methematical concept, it is certainly not a science (as in computer science) because it lacks the first requirement of any science: that it is falsifiable: there's no implementation of a relational dbms against which we can check its claims.
It's pure snake oil.
" ... contrary to OOP."
And contrary to OOP, the relational model has never been implemented.
Buy a book on SQL and get productive.
Leave the relational model to unproductive theorists.
请参阅此和这个。 显然,您可以将 C# 与五种不同的编程范式一起使用,将 C++ 与三种不同的编程范式一起使用,等等。
软件构建与基础物理不同。 物理学致力于使用可能受到新实验数据和/或理论挑战的范式来描述现实。 物理学是一门寻找“真理”的科学,而软件构造则不然。
软件建设是一门生意。 您需要富有成效,即实现一些有人愿意付费的目标。 使用范式是因为它们对于有效地生产软件很有用。 你不需要每个人都同意。 如果我做 OOP 并且它对我来说效果很好,我不在乎“新”范式是否可能对我有用 20%,如果我有时间和金钱来学习它,然后重新思考整个软件结构我'我正在从头开始工作并重新设计它。
另外,你可能使用另一种范式,我仍然会很高兴,就像我可以经营一家日本餐馆赚钱,你可以通过隔壁的墨西哥餐馆赚钱一样。 我不需要和你讨论日本菜是否比墨西哥菜更好。
See this and this. Apparently you can use C# with five different programming paradigms, C++ with three, etc.
Software construction is not akin to Fundamental Physics. Physics strive to describe reality using paradigms which may be challenged by new experimental data and/or theories. Physics is a science which searches for a "truth", in a way that Software construction doesn't.
Software construction is a business. You need to be productive, i.e. to achieve some goals for which someone will pay money. Paradigms are used because they are useful to produce software effectively. You don't need everyone to agree. If I do OOP and it's working well for me, I don't care if a "new" paradigm would potentially be 20% more useful to me if I had the time and money to learn it and later rethink the whole software structure I'm working in and redesign it from scratch.
Also, you may be using another paradigm and I'll still be happy, in the same way that I can make money running a Japanese food restaurant and you can make money with a Mexican food restaurant next door. I don't need to discuss with you whether Japanese food is better than Mexican food.
我怀疑 OOP 会很快消失,它太适合我们的问题和思维模式了。
我们开始看到的是多范式方法,声明性和功能性的想法被纳入面向对象的设计中。 大多数较新的 JVM 语言(JavaFX、Scala、Clojure 等)以及 .net 平台上的 LINQ 和 F# 都是一个很好的例子。
需要注意的是,我在这里并不是在谈论取代 OO,而是在谈论对其的补充。
JavaFX 已经表明声明式
解决方案超越 SQL 和 XSLT,
也可用于绑定
视觉之间的属性和事件
GUI 中的组件
用于容错和高度
并发系统,函数式
编程非常非常适合,
正如爱立信所证明的
AXD301(使用 Erlang 编程)
所以...随着并发变得越来越重要并且 FP 变得越来越流行,我想不支持这种范例的语言将会受到影响。 这包括许多当前流行的语言,例如 C++、Java 和 Ruby,不过 JavaScript 应该可以很好地应对。
I doubt OOP is going away any time soon, it just fits our problems and mental models far too well.
What we're starting to see though is multi-paradigm approaches, with declarative and functional ideas being incorporated into object oriented designs. Most of the newer JVM languages are a good example of this (JavaFX, Scala, Clojure, etc.) as well as LINQ and F# on the .net platform.
It's important to note that I'm not talking about replacing OO here, but about complementing it.
JavaFX has shown that a declarative
solution goes beyond SQL and XSLT,
and can also be used for binding
properties and events between visual
components in a GUI
For fault tolerant and highly
concurrent systems, functional
programming is a very good fit,
as demonstrated by the Ericsson
AXD301 (programmed using Erlang)
So... as concurrency becomes more important and FP becomes more popular, I imagine that languages not supporting this paradigm will suffer. This includes many that are currently popular such as C++, Java and Ruby, though JavaScript should cope very nicely.
使用 OOP 使代码更易于管理(如修改/更新/添加新功能)和理解。 对于较大的项目尤其如此。 由于模块/对象封装了它们的数据和对该数据的操作,因此更容易理解功能和总体情况。
OOP 的好处是更容易(与其他开发人员/管理人员/客户)讨论 LogManager 或 OrderManager,其中每个都包含特定的功能,然后描述“一组将数据转储到文件中的方法”和“方法”跟踪订单详细信息”。
因此,我认为 OOP 尤其对大型项目很有帮助,但总会出现新的概念,因此请在未来不断寻找新的东西,评估并保留有用的东西。
Using OOP makes the code easier to manage (as in modify/update/add new features) and understand. This is especially true with bigger projects. Because modules/objects encapsulate their data and operations on that data it is easier to comprehend the functionality and the big picture.
The benefit of OOP is that it is easier to discuss (with other developers/management/customer) a LogManager or OrderManager, each of which encompass specific functionality, then describing 'a group of methods that dump the data in file' and 'the methods that keep track of order details'.
So I guess OOP is helpful especially with big projects but there are always new concepts turning up so keep on lookout for new stuff in the future, evaluate and keep what is useful.
人们喜欢将各种事物视为“对象”并对它们进行分类,因此毫无疑问OOP如此受欢迎。 然而,在某些领域 OOP 还没有获得更大的普及。 大多数系统使用关系数据库而不是客观数据库。 即使第二个模型拥有一些值得注意的记录并且更适合某些类型的任务,但由于其受欢迎程度、工具的可用性、支持以及关系模型实际上是一个数学概念,而不是关系模型,人们通常会选择关系模型。面向对象编程。
我从未见过 OOP 的另一个领域是软件构建过程。 所有配置和 make 脚本都是过程性的,部分原因是 shell 语言缺乏对 OOP 的支持,部分原因是 OOP 对于此类任务来说过于复杂。
People like to think of various things as "objects" and classify them, so no doubt that OOP is so popular. However, there are some areas where OOP has not gained a bigger popularity. Most of the systems use relational databases rather than objective. Even if the second ones hold some notable records and are better for some types of tasks, the relational model is unsually chosen due to its popularity, availability of tools, support and the fact that the relational model is in fact a mathematical concept, contrary to OOP.
Another area where I have never seen OOP is the software building process. All the configuration and make scripts are procedural, partially because of the lack of the support for OOP in shell languages, partially because OOP is too complex for such tasks.
我的观点略有争议,但我不认为 OOP(至少是现在广泛应用的一种)对于在我的特定领域(VFX,即视觉特效)制作最大规模的软件有帮助在场景组织和应用程序状态方面与游戏有些相似)。 我发现它对于中小型规模非常有用。 我在这里必须要小心一点,因为我过去邀请过一些小怪,但我应该限定这是我在特定类型领域的狭隘经验。
我经常发现的困难是,如果你有所有这些封装数据的小具体对象,那么它们现在都想互相交谈。 它们之间的交互可能变得极其复杂,如下所示(除了在跨越数千个对象的实际应用程序中更加复杂):
这并不是与耦合直接相关的依赖关系图“交互图”。 可能存在抽象来将这些具体对象彼此解耦。
Foo
可能不会直接与Bar
对话。 它可能会通过 IBar 或类似的方式与之对话。 该图仍将Foo
连接到Bar
,因为尽管是解耦的,但它们仍然相互通信。构成自己小生态系统的中小型对象之间的所有这些通信,如果应用于我的领域中大型代码库的整个规模,可能会变得极其难以维护。 而且它变得如此难以维护,因为很难推理对象之间的所有这些交互会发生什么,比如副作用。
相反,我发现有用的是将整个代码库组织成完全独立的、访问中央“数据库”的庞大子系统。 然后每个子系统输入和输出数据。 其他一些子系统可能会访问相同的数据,但没有任何一个系统直接相互通信。
...或此:
...并且每个单独的系统不再尝试封装状态。 它并不试图成为自己的生态系统。 相反,它在中央数据库中读取和写入数据。
当然在每个子系统的实现中,他们可能会使用一些对象来帮助实现它们。 这就是我发现 OOP 在这些子系统的实现中非常有用的地方。 但这些子系统中的每一个都构成了一个相对中小型的项目,不是太大,而正是在中小型项目中,我发现 OOP 非常有用。
用最少的知识进行“装配线编程”
这使得每个子系统都可以专注于做自己的事情,而几乎不知道外界正在发生什么。 专注于物理的开发人员只需坐下来研究物理子系统,对软件的工作原理知之甚少,除了有一个中央数据库,他可以从中检索运动组件(仅数据)等内容,并通过将物理应用于该数据来转换它们。 这使得他的工作变得非常简单,并且使得他可以用最少的关于其他事情如何运作的知识来完成他最擅长的事情。 输入中央数据和输出中央数据:这是每个子系统必须正确执行的所有操作,才能使其他一切正常工作。 这是我在我的领域中发现的最接近“装配线编程”的东西,每个开发人员都可以用最少的关于整个系统如何工作的知识来完成自己的事情。
由于每个子系统的关注范围较窄,测试仍然相当简单。 我们不再通过依赖注入来模拟具体对象,而是生成与特定系统相关的最少量的数据,并测试特定系统是否为给定的输入提供正确的输出。 由于需要测试的系统很少(只需几十个就可以组成一个复杂的软件),因此还大大减少了所需的测试数量。
打破封装
然后,系统变成一个相当扁平的管道,通过几乎不知道彼此存在的独立子系统来转换中央应用程序状态。 有时可能会将一个中心事件推送到另一个系统处理的数据库,但另一个系统仍然不知道该事件来自哪里。 我发现这是至少在我的领域解决复杂性的关键,并且它是通过实体组件系统有效实现的。
然而,它类似于更广泛的过程或函数式编程,以解耦所有这些子系统,并让它们在对外部世界了解最少的情况下工作,因为我们正在打破封装以实现这一目标并避免要求系统与每个子系统进行对话其他。 当您放大时,您可能会发现您所共享的对象被用于实现这些子系统中的任何一个,但从最广泛的范围来看,这些系统类似于 OOP 以外的东西。
全局数据
我必须承认,一开始我对于将 ECS 应用到我所在领域的架构设计中非常犹豫,因为首先,据我所知,流行的商业竞争对手(3DS Max、SoftImage 等),其次,它看起来像是一大堆全局可访问的数据。
然而,我发现这并不是一个大问题。 我们仍然可以非常有效地维护不变量,甚至可能比以前更好。 原因在于 ECS 将所有内容组织成系统和组件的方式。 您可以放心,音频系统不会尝试改变运动组件,例如,即使在最黑客的情况下也不会。 即使团队协调不力,ECS 也不太可能降级为您无法再推断哪些系统访问哪个组件的情况,因为这在纸面上相当明显,并且实际上没有任何理由让某个系统访问不合适的组件。
相反,它常常消除了许多以前对数据完全开放的黑客事物的诱惑,因为在我们以前的代码库中,在松散的协调和关键时间下所做的许多黑客行为都是在匆忙尝试X射线抽象并试图访问物体生态系统的内部。 由于人们匆忙地试图获取他们想要访问的数据并用他们想要访问的数据做事,抽象开始变得漏洞百出。 他们基本上是在尝试只访问数据,这导致界面设计迅速退化。
由于系统的组织方式,仍然存在一些隐约类似于封装的东西,因为通常只有一个系统修改特定类型的组件(在某些特殊情况下有两个系统)。 但他们不拥有该数据,也不提供检索该数据的功能。 系统之间不相互通信。 它们都通过中央 ECS 数据库进行操作(这是必须注入所有这些系统的唯一依赖项)。
灵活性和可扩展性
这已经在有关实体组件系统的外部资源中得到了广泛讨论,但它们在适应全新的设计理念方面非常灵活
事后看来,即使是突破性的概念,比如对一种生物的建议,包括哺乳动物、昆虫和在阳光下同时发芽的植物。
原因之一是没有需要打破的中心抽象。 如果您需要更多数据,或者只是创建一个将植物、哺乳动物和昆虫所需的组件串在一起的实体,则可以引入一些新组件。 设计用于处理昆虫、哺乳动物和植物组件的系统然后会自动拾取它,除了添加一行代码以使用新的组件组合实例化实体之外,您可能会获得所需的行为,而无需进行任何更改。 当您需要全新的功能时,您只需添加一个新系统或修改现有系统即可。
我在其他地方没有发现太多讨论的是,即使在没有我们未能预料到的突破性设计更改的情况下,这也能在多大程度上简化维护。 即使忽略 ECS 的灵活性,当您的代码库达到一定规模时,它也确实可以简化事情。
将对象转化为数据
在之前的 OOP 密集型代码库中,我发现维护接近上面第一张图的代码库非常困难,所需的代码量呈爆炸式增长,因为类比的
Car
在此图中:...必须构建为实现多个接口的完全独立的子类型(类)。 因此,我们系统中的对象数量呈爆炸性增长:一个单独的点光源对象与方向光对象,一个单独的鱼眼相机对象,等等。我们有数千个对象,以无限的组合实现了几十个抽象接口。
当我将它与 ECS 进行比较时,只需要数百个,并且我们能够在使用一小部分代码之前执行完全相同的操作,因为这将类似的
Car
实体变成了不再需要的东西它的阶级。 它变成了组件数据的简单集合,作为一种Entity
类型的通用实例。OOP 替代方案
因此,在某些情况下,在最广泛的设计级别上过度应用 OOP 可能会开始真正降低可维护性。 从最广泛的鸟瞰系统的角度来看,它可以帮助将其扁平化,而不是尝试将其建模得如此“深入”,即对象与对象之间的交互,无论多么抽象。
比较我过去和现在开发的两个系统,新的系统功能更多,但需要数十万的 LOC。 前者需要超过 2000 万个 LOC。 当然,这不是最公平的比较,因为前一个系统有巨大的遗产,但如果你从两个系统中提取一部分,它们在功能上相当相同,没有遗留的包袱(至少与我们可能得到的接近相同),那么ECS 只需要一小部分代码即可完成相同的操作,部分原因是它通过将系统中的类转换为原始数据(组件)的集合(实体),并由大型系统来处理它们,从而显着减少了系统中的类数量一船小/中型物体。
这绝非闻所未闻。 例如,我上面描述的系统广泛用于游戏中。 这在我的领域中相当罕见(我的领域中的大多数架构都是类似 COM 的纯接口,这就是我过去研究过的架构类型),但我发现凝视游戏玩家在做什么时设计一个架构会带来很大的不同,因为它能够创造出在不断发展的过程中仍然非常容易理解的东西。
也就是说,有些人认为 ECS 本身就是一种面向对象编程。 如果是这样,它就不像我们大多数人想象的那种 OOP,因为数据(组件和组成它们的实体)和功能(系统)是分离的。 它需要放弃广泛系统级别的封装,这通常被认为是 OOP 最基本的方面之一。
高级编码
如果您可以将一个应用程序与非常高级的代码拼凑在一起,那么就您的团队必须维护的代码而言,它的规模往往相当小或中等,并且可能可以使用 OOP 非常有效地组装。
在我的视觉特效领域,我们经常要做一些相对较低级别的事情,如光线追踪、图像处理、网格处理、流体动力学等,并且不能只是将第三方产品拼凑在一起,因为我们实际上是在竞争更多的是我们在底层可以做的事情(用户对尖端的、有竞争力的生产渲染改进比更好的 GUI 更感兴趣)。 因此,可能有很多很多代码,从非常低级的位和字节重组到脚本编写者通过嵌入式脚本语言编写的非常高级的代码。
通信互联网络
但是,任何类型的应用程序(高级、低级或组合)都具有足够大的规模,它围绕着一个非常复杂的中央应用程序状态,我在其中发现尝试将所有内容封装到对象中不再有用。 这样做往往会增加复杂性,并且由于事物之间发生的交互量成倍增加而难以推理正在发生的事情。 如果没有足够大的规模的断点,我们停止将每个事物建模为必须相互交谈的封装生态系统,那么推理数千个生态系统相互交谈就不再那么容易了。 即使每个单独都很简单,但将所有内容作为一个整体考虑可能会开始超出我们的头脑,并且我们经常必须考虑大量内容来进行更改、添加新功能和调试内容等等,如果您尝试仅围绕 OOP 原则来设计整个大型系统。 它可以帮助至少在某些领域打破某种规模的封装。
到那时,让一个物理系统封装自己的数据就不一定那么有用了(否则很多东西可能想要与它交谈并检索该数据以及使用适当的输入数据对其进行初始化),这就是我发现通过 ECS 的这种替代方案非常有用,因为它将类比物理系统和所有此类大型系统转变为“中央数据库转换器”或“输出新内容的中央数据库读取器”,现在它们可以相互忽略。 然后,每个系统开始更像是扁平管道中的进程,而不是形成非常复杂的通信图中的节点的对象。
Slightly controversial opinion from me but I don't find OOP, at least of a kind that is popularly applied now, to be that helpful in producing the largest scale software in my particular domain (VFX, which is somewhat similar in scene organization and application state as games). I find it very useful on a medium to smaller scale. I have to be a bit careful here since I've invited some mobs in the past, but I should qualify that this is in my narrow experience in my particular type of domain.
The difficulty I've often found is that if you have all these small concrete objects encapsulating data, they now want to all talk to each other. The interactions between them can get extremely complex, like so (except much, much more complex in a real application spanning thousands of objects):
And this is not a dependency graph directly related to coupling so much as an "interaction graph". There could be abstractions to decouple these concrete objects from each other.
Foo
might not talk toBar
directly. It might instead talk to it throughIBar
or something of this sort. This graph would still connectFoo
toBar
since, albeit being decoupled, they still talk to each other.And all this communication between small and medium-sized objects which make up their own little ecosystem, if applied to the entire scale of a large codebase in my domain, can become extremely difficult to maintain. And it becomes so difficult to maintain because it's hard to reason about what happens with all these interactions between objects with respect to things like side effects.
Instead what I've found useful is to organize the overall codebase into completely independent, hefty subsystems that access a central "database". Each subsystem then inputs and outputs data. Some other subsystems might access the same data, but without any one system directly talking to each other.
... or this:
... and each individual system no longer attempts to encapsulate state. It doesn't try to become its own ecosystem. It instead reads and writes data in the central database.
Of course in the implementation of each subsystem, they might use a number of objects to help implement them. And that's where I find OOP very useful is in the implementation of these subsystems. But each of these subsystems constitutes a relatively medium to small-scale project, not too large, and it's at that medium to smaller scale that I find OOP very useful.
"Assembly-Line Programming" With Minimum Knowledge
This allows each subsystem to just focus on doing its thing with almost no knowledge of what's going on in the outside world. A developer focusing on physics can just sit down with the physics subsystem and know little about how the software works except that there's a central database from which he can retrieve things like motion components (just data) and transform them by applying physics to that data. And that makes his job very simple and makes it so he can do what he does best with the minimum knowledge of how everything else works. Input central data and output central data: that's all each subsystem has to do correctly for everything else to work. It's the closest thing I've found in my field to "assembly line programming" where each developer can do his thing with minimum knowledge about how the overall system works.
Testing is still also quite simple because of the narrow focus of each subsystem. We're no longer mocking concrete objects with dependency injection so much as generating a minimum amount of data relevant to a particular system and testing whether the particular system provides the correct output for a given input. With so few systems to test (just dozens can make up a complex software), it also reduces the number of tests required substantially.
Breaking Encapsulation
The system then turns into a rather flat pipeline transforming central application state through independent subsystems that are practically oblivious to each other's existence. One might sometimes push a central event to the database which another system processes, but that other system is still oblivious about where that event came from. I've found this is the key to tackling complexity at least in my domain, and it is effectively through an entity-component system.
Yet it resembles something closer to procedural or functional programming at the broad scale to decouple all these subsystems and let them work with minimal knowledge of the outside world since we're breaking encapsulation in order to achieve this and avoid requiring the systems to talk to each other. When you zoom in, then you might find your share of objects being used to implement any one of these subsystems, but at the broadest scale, the systems resembles something other than OOP.
Global Data
I have to admit that I was very hesitant about applying ECS at first to an architectural design in my domain since, first, it hadn't been done before to my knowledge in popular commercial competitors (3DS Max, SoftImage, etc), and second, it looks like a whole bunch of globally-accessible data.
I've found, however, that this is not a big problem. We can still very effectively maintain invariants, perhaps even better than before. The reason is due to the way the ECS organizes everything into systems and components. You can rest assured that an audio system won't try to mutate a motion component, e.g., not even under the hackiest of situations. Even with a poorly-coordinated team, it's very improbable that the ECS will degrade into something where you can no longer reason about which systems access which component, since it's rather obvious on paper and there are virtually no reasons whatsoever for a certain system to access an inappropriate component.
To the contrary it often removed many of the former temptations for hacky things with the data wide open since a lot of the hacky things done in our former codebase under loose coordination and crunch time was done in hasty attempts to x-ray abstractions and try to access the internals of the ecosystems of objects. The abstractions started to become leaky as a result of people, in a hurry, trying to just get and do things with the data they wanted to access. They were basically jumping through hoops trying to just access data which lead to interface designs degrading quickly.
There is something vaguely resembling encapsulation still just due to the way the system is organized since there's often only one system modifying a particular type of components (two in some exceptional cases). But they don't own that data, they don't provide functions to retrieve that data. The systems don't talk to each other. They all operate through the central ECS database (which is the only dependency that has to be injected into all these systems).
Flexibility and Extensibility
This is already widely-discussed in external resources about entity-component systems but they are extremely flexible at adapting to radically new design ideas
in hindsight, even concept-breaking ones like a suggestion for a creature which is a mammal, insect, and plant that sprouts leaves under sunlight all at once.
One of the reasons is because there are no central abstractions to break. You introduce some new components if you need more data for this or just create an entity which strings together the components required for a plant, mammal, and insect. The systems designed to process insect, mammal, and plant components then automatically pick it up and you might get the behavior you want without changing anything besides adding a line of code to instantiate an entity with a new combo of components. When you need whole new functionality, you just add a new system or modify an existing one.
What I haven't found discussed so much elsewhere is how much this eases maintenance even in scenarios when there are no concept-breaking design changes that we failed to anticipate. Even ignoring the flexibility of the ECS, it can really simplify things when your codebase reaches a certain scale.
Turning Objects Into Data
In a previous OOP-heavy codebase where I saw the difficulty of maintaining a codebase closer to the first graph above, the amount of code required exploded because the analogical
Car
in this diagram:... had to be built as a completely separate subtype (class) implementing multiple interfaces. So we had an explosive number of objects in the system: a separate object for point lights from directional lights, a separate object for a fish eye camera from another, etc. We had thousands of objects implementing a few dozen abstract interfaces in endless combinations.
When I compared it to ECS, that required only hundreds and we were able to do the exact same things before using a small fraction of the code, because that turned the analogical
Car
entity into something that no longer requires its class. It turns into a simple collection of component data as a generalized instance of just oneEntity
type.OOP Alternatives
So there are cases like this where OOP applied in excess at the broadest level of the design can start to really degrade maintainability. At the broadest birds-eye view of your system, it can help to flatten it and not try to model it so "deep" with objects interacting with objects interacting with objects, however abstractly.
Comparing the two systems I worked on in the past and now, the new one has more features but takes hundreds of thousands of LOC. The former required over 20 million LOC. Of course it's not the fairest comparison since the former one had a huge legacy, but if you take a slice of the two systems which are functionally quite equal without the legacy baggage (at least about as close to equal as we might get), the ECS takes a small fraction of the code to do the same thing, and partly because it dramatically reduces the number of classes there are in the system by turning them into collections (entities) of raw data (components) with hefty systems to process them instead of a boatload of small/medium objects.
It's far from unheard of. The system I'm describing above, for example, is widely used in games. It's quite rare in my field (most of the architectures in my field are COM-like with pure interfaces, and that's the type of architecture I worked on in the past), but I've found that peering over at what gamers are doing when designing an architecture made a world of difference in being able to create something that still remains very comprehensible at it grows and grows.
That said, some people consider ECS to be a type of object-oriented programming on its own. If so, it doesn't resemble OOP of a kind most of us would think of, since data (components and entities to compose them) and functionality (systems) are separated. It requires abandoning encapsulation at the broad system level which is often considered one of the most fundamental aspects of OOP.
High-Level Coding
If you can piece together an application with very high-level code, then it tends to be rather small or medium in scale as far as the code your team has to maintain and can probably be assembled very effectively using OOP.
In my field in VFX, we often have to do things that are relatively low-level like raytracing, image processing, mesh processing, fluid dynamics, etc, and can't just piece these together from third party products since we're actually competing more in terms of what we can do at the low-level (users get more excited about cutting-edge, competitive production rendering improvements than, say, a nicer GUI). So there can be lots and lots of code ranging from very low-level shuffling of bits and bytes to very high-level code that scripters write through embedded scripting languages.
Interweb of Communication
But there comes a point with a large enough scale with any type of application, high-level or low-level or a combo, that revolves around a very complex central application state where I've found it no longer useful to try to encapsulate everything into objects. Doing so tends to multiply complexity and the difficulty to reason about what goes on due to the multiplied amount of interaction that goes on between everything. It no longer becomes so easy to reason about thousands of ecosystems talking to each other if there isn't a breaking point at a large enough scale where we stop modeling each thing as encapsulated ecosystems that have to talk to each other. Even if each one is individually simple, everything taken in as a whole can start to more than overwhelm the mind, and we often have to take a whole lot of that in to make changes and add new features and debug things and so forth if you try to revolve the design of an entire large-scale system solely around OOP principles. It can help to break free of encapsulation at some scale for at least some domains.
At that point it's not necessarily so useful anymore to, say, have a physics system encapsulate its own data (otherwise many things could want to talk to it and retrieve that data as well as initialize it with the appropriate input data), and that's where I found this alternative through ECS so helpful, since it turns the analogical physics system, and all such hefty systems, into a "central database transformer" or a "central database reader which outputs something new" which can now be oblivious about each other. Each system then starts to resemble more like a process in a flat pipeline than an object which forms a node in a very complex graph of communication.