他担心的是,大量的课程将转化为维护的噩梦。我的看法是,它将产生完全相反的效果。
我绝对站在您朋友的一边,但这可能取决于我们的领域以及我们要解决的问题和设计的类型,尤其是将来可能需要更改的类型。不同的问题,不同的解决方案。我不相信对与错,只是程序员试图寻找能够最好地解决其特定设计问题的最佳方法。我使用的是VFX,与游戏引擎不太相似。
但是,我所苦苦挣扎的问题可能至少可以归结为“太多的类”或“太多的功能”,因为我至少在某种程度上被称为符合SOLID的体系结构(基于COM)。您的朋友可能会形容。我要特别指出的是,“互动过多,太多的地方可能会导致行为不端,太多的地方可能会导致副作用,太多的地方可能需要更改,太多的地方可能无法实现我们认为的目标”。
我们有很多抽象(纯的)接口是由大量子类型实现的,就像这样(在谈论ECS好处的背景下制作此图,而无视左下角的注释):
运动接口或场景节点接口可能由数百个子类型实现:灯光,相机,网格,物理求解器,着色器,纹理,骨骼,基本形状,曲线等(每种类型通常都有多种类型) )。最终的问题确实是这些设计不是那么稳定。我们的需求在不断变化,有时接口本身也必须在变化,而当您想更改由200个子类型实现的抽象接口时,这是一笔极其昂贵的更改。我们开始通过使用抽象基类来减轻这种负担,在它们之间减少了此类设计更改的成本,但它们仍然很昂贵。
因此,我也开始探索游戏行业中相当普遍使用的实体组件系统架构。那改变了一切,就像这样:
哇!就可维护性而言,是如此的不同。依赖关系不再流向抽象,而是流向数据(组件)。至少就我而言,尽管需求不断变化(尽管我们对相同数据的处理能力会随着需求的变化而不断变化),但从设计的角度来讲,数据要稳定得多并且更容易获得正确的结果。
另外,由于ECS中的实体使用组合而不是继承,因此它们实际上不需要包含功能。它们只是类比的“组件容器”。这就使得实现运动接口的类比200子类型变成200个实体实例(不是具有单独代码的单独类型),它们仅存储运动分量(除了与运动相关的数据外,什么也没有)。A PointLight
不再是单独的类/子类型。这根本不是一堂课。它是一个实体的实例,该实体仅组合了与其在空间中的位置(运动)和点光源的特定属性有关的某些组件(数据)。与它们关联的唯一功能是在系统内部,例如RenderSystem
,它会在场景中寻找灯光组件以确定如何渲染场景。
随着ECS方法下需求的变化,通常只需要更改在该数据上运行的一个或两个系统,或者只是在侧面引入新系统,或者在需要新数据时引入新组件。
因此,至少对于我的领域,而且我几乎可以肯定,并不是每个人都这样,这使事情变得如此容易,因为依赖关系正在朝着稳定的方向发展(不需要经常更改的事物)。当依赖关系统一流向抽象时,在COM体系结构中情况并非如此。以我为例,要弄清楚运动的前期需要什么数据要容易得多,而不是您可以使用它做所有可能的事情,随着新要求的出现,这种变化通常会在几个月或几年内有所变化。
在OOP中是否存在某些或全部SOLID原则不适合清理代码的情况?
好吧,我不能说干净的代码,因为有些人将干净的代码等同于SOLID,但是肯定在某些情况下,像ECS一样,将数据与功能分开,并且将依赖关系从抽象重定向到数据肯定可以使事情变得容易得多。出于明显的耦合原因,如果数据比抽象要稳定得多,请进行更改。当然,对数据的依赖性可能会使保持不变性变得困难,但是ECS倾向于通过系统组织将这种情况减少到最小,这可以最小化访问任何给定类型的组件的系统数量。
不一定像DIP所建议的那样,依赖关系应该流向抽象。依赖关系应该流向不太可能需要将来更改的事物。在所有情况下都可能是抽象,也可能不是抽象(当然这不是我的意思)。
- 是的,存在与SOLID部分冲突的OOP设计原则
- 是的,存在与SOLID完全冲突的OOP设计原则。
我不确定ECS是否真的是OOP的味道。有人用这种方式定义它,但我认为它在本质上与耦合特性,数据(组件)与功能(系统)的分离以及缺乏数据封装的本质不同。如果将其视为OOP的一种形式,我认为它与SOLID(至少是SRP,开放式/封闭式,liskov替代和DIP的最严格的观念)有很大冲突。但是我希望这是一个案例和领域的合理示例,在该案例和领域中,SOLID的最基本方面(至少人们通常会在更易于理解的OOP上下文中对其进行解释)可能不太适用。
小班
我正在解释我的一款游戏的体系结构,令我的朋友惊讶的是,它包含许多小类和几个抽象层。我认为这是我专注于赋予一切单一职责并放松组件之间的耦合的结果。
ECS挑战并改变了我的观点。像您一样,我以前一直认为可维护性的思想是对可能的事物进行最简单的实现,这意味着许多事物,此外还包含许多相互依赖的事物(即使相互依赖是抽象之间的)。如果您仅放大一个类或函数以查看最直接,最简单的实现,那么这是最有意义的;如果看不到,请对其进行重构,甚至进一步分解。但是很容易错过结果,因为外部世界每次将相对复杂的事物分解为2个或更多事物时,这2个或更多事物必然不可避免地在某些情况下相互交互*(见下文)方式,否则外界必须与所有人互动。
这些天来,我发现在某种事物的简单性与多少事物之间以及在需要多少互动之间存在一种平衡的行为。ECS中的系统往往非常繁重,并且采用非平凡的实现来对数据进行操作,例如PhysicsSystem
或RenderSystem
或GuiLayoutSystem
。但是,一个复杂的产品只需要很少的一个这一事实,往往会使后退变得容易,并且可以对整个代码库的整体行为进行推理。那里的东西可能暗示着,依靠更少,更庞大的类(仍然履行可以说是单一的责任)的观点可能不是一个坏主意,如果这意味着要维护和推理的类更少,并且整个过程中的交互更少系统。
互动互动
我说的是“交互”而不是“耦合”(尽管减少交互意味着减少两者),因为您可以使用抽象来解耦两个具体的对象,但是它们仍然可以相互通信。在这种间接交流的过程中,它们仍可能导致副作用。通常,我发现推理系统正确性的能力与这些“相互作用”有关,而与“耦合”无关。尽量减少交互作用,往往会使我更容易从鸟瞰图上推理出一切。这意味着事情根本不会互相交谈,从这个意义上讲,ECS还趋向于将“交互”真正地最小化,而不仅仅是耦合到最小的栏(至少我没有)。
也就是说,这可能至少部分是我和我的个人弱点。我发现创建最大规模系统的最大障碍是,仍然自信地对它们进行推理,浏览它们,并觉得我可以以可预测的方式在任何地方进行任何潜在的所需更改,包括状态和资源管理以及副作用。当我从成千上万的LOC到成千上万的LOC到数百万的LOC时,这是最大的障碍,即使对于我自己编写的代码也是如此。如果有什么事情会使我无所适从,那就是从某种意义上说,我不再能够理解应用程序状态,数据和副作用方面的情况。它' 进行变更所需的时间不是机械时间,它使我的工作减慢了很多,而如果系统超出了我的思维能力,那么就无法理解变更的全部影响。对我来说,减少交互是最有效的方法,它可以使产品具有更大的功能而变得更大,而我个人不会被这些事情淹没,因为将交互减少到最低程度同样会减少可以甚至可能会更改应用程序状态并引起副作用。
它可以变成这样(图中的所有功能都可以正常运行,显然,在现实世界中场景中对象的数量是很多倍,这是一个“交互”图,而不是耦合图)。一个介于两者之间的抽象):
...仅在系统具有功能的情况下(蓝色组件现在仅是数据,现在是耦合图):
关于这一切的想法不断涌现,也许是一种在与SOLID更兼容的更加一致的OOP上下文中构架这些好处的一种方法,但是我还没有找到设计和文字,我发现了因为我习惯于抛弃所有与OOP直接相关的术语,所以很难。我一直在努力弄清这里的人们的答案,并尽我最大的努力制定自己的答案,但是关于ECS的本质,有些事情非常有趣,但我无法完全将其付诸实践。甚至可能适用于不使用它的体系结构。我也希望这个答案不会作为ECS推广而来!我觉得这很有趣,因为设计ECS确实彻底改变了我的想法,