低耦合和紧密的凝聚力

当然这取决于情况。 但是当一个较低级别的对象或系统与一个更高级别的系统进行通信时,应该使用callback函数还是事件来保持指向更高级别对象的指针呢?

例如,我们有一个world级的成员variablesvector<monster> monsters 。 当monster类与world通信,我应该更喜欢使用callback函数,或者我应该有一个指向monster类内的world类的指针?

一个class级可以与另一个class级紧密结合的方式有三种主要方式:

  1. 通过callback函数。
  2. 通过一个事件系统。
  3. 通过一个接口。

三者是密切相关的。 事件系统在许多方面只是一个callback列表。 callback或多或less是一个单一方法的接口。

在C ++中,我很less使用callback:

  1. C ++对于保留this指针的callback没有很好的支持,所以很难在面向对象的代码中使用callback函数。

  2. callback基本上是一个不可扩展的单方法接口。 随着时间的推移,我发现我几乎总是需要不止一种方法来定义这个接口,而且单个callback是不够的。

在这种情况下,我可能会做一个接口。 在你的问题中,你实际上并没有说出什么monster实际需要与world沟通。 猜测,我会做这样的事情:

 class IWorld { public: virtual Monster* getNearbyMonster(const Position & position) = 0; virtual Item* getItemAt(const Position & position) = 0; }; class Monster { public: void update(IWorld * world) { // Do stuff... } } class World : public IWorld { public: virtual Monster* getNearbyMonster(const Position & position) { // ... } virtual Item* getItemAt(const Position & position) { // ... } // Lots of other stuff that Monster should not have access to... } 

这里的想法是,你只能放入IWorld (这是一个糟糕的名字), Monster需要访问的最低限度。 它对世界的看法应该尽可能地狭窄。

不要使用callback函数来隐藏你正在调用的函数。 如果您要放入一个callback函数,并且该callback函数将有一个且只有一个函数分配给它,那么你根本就没有打破耦合。 你只是用另一层抽象来掩盖它。 你没有获得任何东西(除了编译时间),但你失去了清晰度。

我不会完全称之为最佳实践,但是有一些东西包含的实体有一个指向父代的指针是一种常见的模式。

也就是说,使用接口模式可能会值得你为怪物提供一个他们可以在世界上调用的有限的function子集。

通常我会尽量避免双向链接,但是如果我必须拥有这些链接,我绝对确信有一种方法可以让他们和一个打破它们,这样你就永远不会有矛盾。

通常,您可以通过在必要时传递数据来完全避免双向链接。 一个微不足道的重构就是为了不让怪物与世界保持联系,而是通过引用需要它的怪物方法来传递世界。 更好的是只通过一个怪物严格需要的世界各地的接口,意味着怪物并不是来依赖于世界的具体实现。 这与接口隔离原理和依赖倒置原则相对应 ,但是并没有开始引入事件,信号+时隙等有时可以获得的多余抽象。

在某种程度上,你可以争辩说,使用callback是一个非常专业的迷你接口,这很好。 您必须决定是否可以通过一个接口对象中的一个方法集合或不同的callback中的多个方法来更有意义地实现您的目标。

我试图避免包含对象调用他们的容器,因为我发现它会导致混淆,变得太容易certificate,它将被过度使用,并创建无法管理的依赖关系。

在我看来,理想的解决scheme是让更高层次的class级足够聪明地管理下层class级。 例如,知道如果一个怪物和一个骑士之间发生碰撞,而不知道另一个怪物是否发生碰撞的世界,比那个问世界是否与骑士相撞的怪物更好。

在你的情况下的另一个select,我可能会弄清楚为什么怪物类需要知道世界级,你很可能会发现,世界上有一些东西可以分解成它自己的类感觉怪物类要知道。

显然,如果没有事件,你不会走得太远,但是在开始写(devise)一个事件系统之前,你应该问你一个真正的问题:为什么这个怪物要和世界级的人交stream? 应该是真的吗?

我们来看一个“古典”的情况,一个攻击玩家的怪物。

怪物正在攻击:世界可以很好地识别英雄在怪物旁边的情况,并告诉怪物进行攻击。 所以怪物的function是:

 void Monster::attack(LivingCreature l) { // Call to combat system } 

但是世界(谁已经知道这个怪物)并不需要被怪物所了解。 实际上,这个怪物可以忽略这个阶级世界的存在,这可能更好。

当怪物正在移动时(我让子系统获得生物并处理移动计算/它的意图,怪物只是一个数据包,但很多人会说这不是真正的OOP)。

我的观点是:事件(或callback)当然是伟大的,但它们并不是你面对的每一个问题的唯一答案。

无论什么时候,我都会尝试将对象之间的通信限制为一个请求 – 响应模型。 对于程序中的对象有一个隐含的部分sorting,这样在任何两个对象A和B之间,可能有一种方法可以直接或间接调用B的方法,或者B可以直接或间接地调用A的方法,但A和B不可能互相呼叫对方的方法。 当然,有时候,你想要与方法的调用者进行反向通信。 有几种方法我喜欢这样做,而且都不是callback。

一种方法是在方法调用的返回值中包含更多信息,这意味着客户端代码在过程返回控制权之后决定如何处理它。

另一种方式是打电话给一个共同的孩子对象。 也就是说,如果A在B上调用一个方法,并且B需要向A传递一些信息,那么B在C上调用一个方法,A和B都可以调用C,但是C不能调用A或B.在B将控制权返回给A之后,负责从C获取信息。请注意,这与我提出的第一种方式并没有根本的不同。 对象A仍然只能从返回值中检索信息; 没有一个对象A的方法是由B或C调用的。这个技巧的一个变种是把C作为parameter passing给方法,但是对C和A和B的关系的限制仍然适用。

现在,重要的问题是为什么我坚持这样做。 主要有三个原因:

  • 它让我的对象更松散耦合。 我的对象可能会封装其他对象,但是它们永远不会依赖调用者的上下文,上下文将永远不依赖于封装的对象。
  • 这使我的控制stream程很容易理解。 能够假设唯一的代码可以改变self的内部状态,而执行方法是一种方法,而不是其他方法。 这是一种可能导致将互斥体放在并发对象上的推理。
  • 它保护我的对象封装的数据不variables。 公共方法被允许依赖于不variables,如果一个方法可以在外部被调用而另一个方法已经在执行,那么这些不variables可能被违反。

我并不反对callback的所有用途。 为了保持我从不“调用调用者”的策略,如果对象A调用B上的方法并将callback传递给它,则callback可能不会更改A的内部状态,并且包含A封装的对象和A的上下文中的对象。 换句话说,callback函数只能调用由B给出的对象的方法。实际上,callback函数与B的约束条件相同。

最后一个松散的结局是,我将允许调用任何纯函数,而不管这个部分的顺序是什么。 纯函数与方法有一点不同,它们不能改变或依赖于可变状态或副作用,所以不用担心它们混淆事物。

亲自? 我只是使用一个单身人士。

是的,好吧,糟糕的devise,而不是面向对象,等等。你知道吗? 我不在乎 。 我正在写一个游戏,而不是技术展示。 没有人会对代码进行评分。 目的是制作一个有趣的游戏,一切以我为中心的游戏都将导致游戏的乐趣减less。

你会有两个世界同时运行吗? 也许! 也许你会的。 但是除非你现在能想到这种情况,否则你可能不会。

所以,我的解决scheme:做一个世界单身人士。 调用它的function。 完成整个混乱。 你可以把一个额外的parameter passing给每一个函数 – 不要犯错误,这是导致这个问题的原因。 或者你可以写一些有用的代码。

这样做需要一点纪律,当它变得杂乱无章时(这是“当”,而不是“如果”),但是没有办法防止代码变得混乱 – 无论是有意大利面条问题,还是数千抽象层的问题。 至less这样你就不会编写大量不必要的代码。

如果你决定不再需要一个单身人士,通常很容易摆脱它。 做了一些工作,需要传递十亿个参数,但是这些参数是你需要传递的参数。