【发布时间】:2015-02-15 03:01:14
【问题描述】:
我的代码库大量使用接口/抽象、工厂、存储库、依赖注入和其他设计模式,以尝试编写好的、可维护的代码。我的 DI 容器是SimpleInjector。但是,我遇到了一个间接循环依赖问题,我正在努力寻找如何在不违反好的设计(以及我自己的原则!)的情况下打破它。
下面的半伪代码代表了问题的简化(注意:为了简洁起见,我这里不详细介绍接口,但可以从实现它们的类中推断出来,我也没有'不显示所有琐碎的东西,如 c'tor 的设置支持字段 - 这是暗示的)。另请注意,我始终使用构造函数注入。
class A : IA
{
A(int id, IBRepository br);
IEnumerable<IB> GetBs(); // uses _br and _id to find all "child" B's.
int _id;
IBRepository _br;
}
class B : IB
{
B(int id, int aId, IARepository ar);
IA GetA(); // uses _ar and _aId to find "parent" A.
int _id;
int _aId;
IARepository _ar;
}
class ARepository : IARepository
{
ARepository(IAFactory af);
IA FindById(int id); // uses _af to create A if Id found.
IAFactory _af;
}
class BRepository : IBRepository
{
BRepository(IBFactory bf);
IB FindById(int id); // uses _bf to create B if Id found.
IBFactory _bf;
}
class AFactory : IAFactory
{
AFactory(IBRepository br);
IA Create(); // _br fed in to A c'tor
IBRepository _br;
}
class BFactory : IBFactory
{
BFactory(IARepository ar);
IB Create(); // _ar fed in to B c'tor
IARepository _ar;
}
下面是一些示例使用代码:
// Sample usage code
IARepository ar = Container.GetInstance<IARepository>();
IA a = ar.FindById(123);
IEnumerable<IB> bs = a.GetBs(); // get children.
IB b = bs.First();
IA a2 = b.GetA() // get parent.
Debug.Assert(a.Id == a2.Id);
通过将具体类注册到它们各自的接口(使用RegisterSingle<T> 用于单例/非瞬态生命周期)来连接容器(未显示)。
问题是我引入了一个间接循环依赖,如此处所示(实际上,当应用程序启动时,问题表现为异常——SimpleInjector DI 容器很好地告诉了您问题!):
// Circular dependency chain
AFactory
BRepository
BFactory
ARepository
AFactory // <-- circular dependency!
如您所见,我已经在 A 和 B 上使用了 ID,以试图减轻在每个类上存储直接对象引用可能引起的循环依赖问题(我之前被这个问题困扰过) )。
我考虑完全打破域模型的父/子关系(A.GetBs() 和 B.GetA())并将该功能推入某种单独的查找服务,但这对我来说似乎是一种代码味道领域模型应该封装它们自己的关系。此外,存储库已经用于这种查找功能的目的(A 和 B 已经拥有)。
但最重要的是,这会使A 和B 的客户端代码更加复杂(消费者习惯于能够无缝地“点”他们通过对象图的方式)。此外,这会损害性能,因为不是在A 和B 上缓存父/子对象引用,客户端代码调用一些单独的服务来进行查找将需要不断地访问后备数据存储(假设没有存储库缓存)和加入 Id。
俗话说,大多数软件问题都可以通过另一个层次的抽象或间接来解决,我敢肯定这里会是这样,但我还没有弄清楚。
我查看了以下内容,虽然我认为我掌握了他们告诉我的内容,但我很难将其与我的问题集联系起来:
- https://softwareengineering.stackexchange.com/questions/253646/how-to-handle-circular-dependency-in-dependency-injection
- http://misko.hevery.com/2008/08/01/circular-dependency-in-constructors-and-dependency-injection/
- how to avoid circular dependencies here
总而言之,我如何在仍然使用构造函数 DI 的同时打破循环依赖问题,坚持并利用已建立的设计模式和最佳实践,并在理想情况下为 A 和 B 的消费者维护一个不错的 API 到“点”他们通过关系的方式?任何帮助将不胜感激。
【问题讨论】:
-
为什么有工厂和仓库?在不知道它们的作用的情况下,很难提出解决方案。您有依赖于您的存储库的工厂和依赖于您的工厂的存储库。
-
尝试使用
Func<IBFactory>和Func<IAFactory>而不是IBFactory/IAFactory- 这样您就可以消除一个额外重定向价格的循环依赖(Unity 同时注册Func类/接口,可能你的容器需要一些额外的代码) -
@CoderDennis 实际上,
A和B需要额外的依赖项(之前未显示/讨论过),因此是工厂。因此,工厂将这些额外的依赖项作为它们自己的依赖项,并在每次调用Create()时将它们提供给A和B的c'tors。存储库从 XML 源加载数据并提供域对象,因此存储库依赖于工厂。例如。GetById()询问 XML 源并使用工厂将查询结果转换为IA、IB实例等。感谢您的帮助。 -
@AlexeiLevenkov 抱歉,我不太明白...我到底应该在哪里使用
Func<IAFactory>/Func<IBFactory>? -
@prlc - 我添加了带有代码的注释作为“答案”(因为没有办法将体面的格式化程序代码放在注释中)。