c++ 依赖注入 + Demeter 法则 + logger/assert

c++ Dependency Injection + Law of Demeter + logger/assert

我看过两个很棒的视频 (this and this),内容涉及依赖注入、得墨忒耳定律和全局状态(单身人士被认为是全局的)。

我想我已经有了基本的想法,但我的库中已经有一些单例 classes。但是,如果我想要一个可测试的 "well-designed" 或 "less coupled" 代码,我应该使用 DI 和 LoD。这当然意味着单例(作为一种设计模式)是邪恶的,因为调用者现在没有实现并且对全局事物的任何依赖都是不好的,至少从测试的角度来看是这样。

更具体地说,我正在构建一个简单的游戏引擎,而不使用任何更大的第 3 方库。这意味着我还必须使用特定于平台的低级代码。

让我们更具体一点。我的图书馆中有一个数学部分,其中有一个 class Vector2。当为其功能之一输入无效数据时,它应该能够 "throw assert"。或者应该能够将其记录为错误。或两者。在此之前,我只是简单地使用了 Singleton<Logger>,所以我可以在任何地方访问它。

但我同意,这些东西不应该被使用,DI解决了这些问题。例如。如果记录器尚未初始化怎么办?如果我想要一个用于测试的虚拟记录器怎么办?等等......你对这些情况有什么建议(比如 Logger 和 Assert classes)?

LoD 还说我不应该对对象使用访问器(比如 getObjectA()->getObjectB()->doSomething())。相反,将它们作为参数传递给 function/constructor。可以让一切更容易测试(和调试),但跳过这些功能可能会很痛苦。

考虑来自 Unity 引擎的示例。 GameObject 具有从该对象获取组件的方法。例如。如果我想手动转换我的对象,我没有选择调用 "object getter",像这样:

this.GetComponent<Transform>().SetPosition(...);

这违反了 LoD,不是吗?

This means I have to work with platform-specific and low level code as well.

使用dependency inversion(不仅仅是注入)

What do you recommend for these cases (like the Logger and Assert classes)?

DI 要求您更改 APIs 以允许您在使用它们的地方注入东西。为避免必须添加大量额外参数(一个用于记录器,一个用于断言实现或全局配置设置等)的情况,请将它们组合在一起:

  • 创建运行时配置class
  • 为其添加服务(记录器服务、验证服务、配置服务等)
  • 传递运行时配置;

Also the LoD says that I should not use accessors for objects (like getObjectA()->getObjectB()->doSomething()). Instead, pass them as a parameter to the function/constructor.

这种类型的调用链存在一些问题:

  • 它鼓励重复(如果你的代码中有很多次 getObjectA()->getObjectB()->,那已经是一个维护问题)

  • 它是不完整设计的糟糕替代品。 LoD 表示,如果您需要 doSomething()ObjectA 的实例开始,那么 ObjectA 应该有一个方法 doSomething:

    void ObjectA::doSomething(ObjectA& a)
    {
        getObjectB()->doSomething();
    }
    

    (或类似)。

    这为扩展"doSomething is done starting from an instance of ObjectA"增加了一个自然点,有利于维护。

  • 它对所有需要 doSomething 的客户端代码强加了它需要了解 ObjectB 接口的事实。这听起来很小,但问题很普遍,当作为设计策略应用时,它会严重复合(如果您的 ObjectA 不仅有 ObjectB,还有 ObjectC 和 ObjectD,这可能足以迫使您花费大量时间时间只是维持依赖关系)。

this.GetComponent<Transform>().SetPosition(...);

This is against the LoD, isn't it?

是的。代码可以这样拆分:

void SetPosition(Transform& t) { t.SetPosition(); }

客户代码:

SetPosition(this.GetComponent<Transform>());

这样,在客户端代码中设置位置,就不用再关心Transform的接口了。您也不关心 void SetPosition(Transform& t) 的实现,有一个名为 GetComponent.

的 API

上面示例的替代 LoD 实现:

void YourObject::SetTransformPositions()
{
    GetComponent<Transformation>.SetPosition();
}

... this 的类型是 YourObject*.