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*
.
我看过两个很棒的视频 (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
.
上面示例的替代 LoD 实现:
void YourObject::SetTransformPositions()
{
GetComponent<Transformation>.SetPosition();
}
... this
的类型是 YourObject*
.