对我来说,这是一个耦合问题,与设计的粒度有关。即使是最松散的耦合形式,也会将依赖性从一件事引入另一件事。如果对数百到数千个对象执行了此操作,即使它们都相对简单,也要遵循SRP,即使所有依赖项都流向稳定的抽象,这也会产生一个很难作为一个相互关联的整体进行推理的代码库。
有一些实用的东西可以帮助您评估代码库的复杂性,这在理论上不经常在SE中进行讨论,例如,在到达终点之前您可以深入到调用堆栈的深度,以及在使用之前需要深入的深度。要有很大的信心,请理解在调用堆栈的该级别可能发生的所有可能的副作用,包括发生异常的情况。
而且,根据我的经验,我发现具有较浅调用堆栈的较扁平的系统往往更容易推理。一个极端的例子是实体组件系统,其中组件只是原始数据。只有系统才具有功能,并且在实施和使用ECS的过程中,我发现它是迄今为止迄今为止最简单的系统,它可以推断何时跨越数十万行代码的复杂代码库基本上会沸腾到几十个包含所有功能。
太多东西无法提供功能
在我以前的代码库中工作之前,另一个选择是一个具有数百到数千个几乎很小的对象的系统,而不是几十个笨重的系统,其中一些对象仅用于将消息从一个对象传递到另一个Message
对象(例如,具有自己的公共接口)。从本质上讲,这就是将ECS恢复到组件具有功能并且实体中组件的每个唯一组合产生其自己的对象类型的程度。这将倾向于产生更小,更简单的功能,这些功能是通过模仿对象的无穷无尽的对象组合(Particle
对象与对象之间的继承和提供)来提供的。Physics System
,例如)。但是,它也往往会产生一个复杂的相互依赖关系图,这使得很难从广义上推断发生了什么,仅仅是因为代码库中有很多事情实际上可以做某件事,因此可以做错什么- -类型不是具有相关功能的“数据”类型,而是“对象”类型。用作纯数据且没有任何关联功能的类型不可能出错,因为它们不能自行执行任何操作。
纯接口对解决此可理解性问题无济于事,因为即使这使“编译时依赖性”变得不那么复杂并且为更改和扩展提供了更大的喘息空间,它也没有使“运行时依赖性”和交互变得没有那么复杂。即使通过调用客户对象,最终仍然会在具体的帐户对象上调用函数IAccount
。多态和抽象接口有其用途,但是它们并没有以真正帮助您推断在任何给定点可能发生的所有副作用的方式将事物解耦。为了实现这种有效的解耦,您需要一个代码库,其中包含功能的东西要少得多。
更多数据,更少功能
因此,即使您没有完全应用ECS方法,我也发现它非常有用,因为它可以将原本数百个对象变成原始数据,而庞大的系统设计更为粗略,可以提供所有功能。它最大化了“数据”类型的数量,并最小化了“对象”类型的数量,因此绝对最小化了系统中实际可能出错的位置数量。最终结果是一个非常“平坦”的系统,没有复杂的依赖关系图,只是系统到组件,从不相反,从没有组件到其他组件。从根本上讲,原始数据更多,而抽象则更少,这具有将代码库的功能集中和扩展到关键区域(关键抽象)的作用。
如果30个较简单的事物相互关联而复杂的事物独立存在,则30个较简单的事物不一定比1个较复杂的事物更容易推理。因此,我的建议实际上是将复杂性从对象之间的交互转移到更大的,不需要与其他任何对象交互以实现大规模去耦的对象上,而不是整个系统(不是整体和上帝对象,请注意,不是使用200种方法的类,而是尽管具有最低限度的接口,但比a Message
或a 更高的级别Particle
)。并支持更普通的旧数据类型。您对这些的依赖越多,获得的耦合就越少。即使这与某些SE想法相矛盾,我发现它确实有很大帮助。