我的代码库大量使用接口/抽象,工厂,存储库,依赖注入和其他设计模式,试图编写好的,可维护的代码。我的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
的客户端代码变得更加复杂(消费者习惯于能够无缝地通过对象图表来点对点#34)。此外,这会损害性能,而不是在A
和B
上缓存父/子对象引用,客户端代码调用某些单独的服务来执行查找将需要不断访问后备数据存储(假设没有存储库缓存)并加入了Id。
俗话说,大多数软件问题都可以通过另一个抽象层次或间接层面来解决,我确信会出现这种情况,但我还没有能够解决这个问题
我已经审核了以下内容,虽然我认为我掌握了他们告诉我的内容,但我却在努力将其与我的问题集联系起来:
总而言之,如何在仍然使用构造函数DI的同时打破循环依赖问题,坚持并利用已建立的设计模式和最佳实践,并为A
和{{1}的消费者提供理想的API维护to to&#34; dot&#34;他们通过关系的方式?非常感谢任何帮助。
答案 0 :(得分:1)
类B
不需要A
工厂或存储库。如果B
是给定A
的子项,则只需将A
(或IA
)的实例传递给B
的构造函数。
您问题中的示例用法将完美无缺。
您必须更改某些内容,因为您的依赖关系图是循环的。您必须确定哪些对象真正依赖于彼此以及哪些对象不相互依赖。要求只有父B
的可用实例才能创建A
s是一种方法。
我真的不明白你的工厂和存储库做了什么。两者似乎对我来说太多了。但这两个存储库是否都依赖于两个工厂?然后每个工厂都没有任何构造函数依赖。
如果需要,工厂Create
方法可以接收存储库实例作为方法参数。
答案 1 :(得分:1)
长评:
您可以通过“添加一个水平的inderectoin”来延迟导致圆圈的对象的分辨率。由于通常你不需要在构造函数中出厂,你可以使用Func<T>
代替T
本身将实际分辨率推迟到你需要的点,其中函数类似于()=> container.Resolve(typeof(T))
:
class AFactory : IAFactory
{
AFactory(Func<IBRepository> br);
IA Create()
{
var _br = _lazyBr();
// _br fed in to A c'tor
...
}
Func<IBRepository> _lazyBr;
}
请注意,Unity DI会自动注册Func<T>
和T
,我不知道如何在您正在使用的容器中强制它,但它应该类似于:
container.Register<Func<IBRepository>>(
() => container.Resolve<IBRepository>());
答案 2 :(得分:0)
在尝试了@CoderDennis和@AlexeiLevenkov提供的非常有用的建议之后,我最终重新评估了我的域模型设计,并通过不再为子类提供访问其父级的属性来打破循环依赖性问题。所以我只是避免了原来的问题,而不是真正解决它,但设计的变化最终对我有用。
感谢两位家伙努力帮助我解决问题并帮助我认识到也许最好的想法是改变我对问题的处理方法。