Unity C#中组合构造器注入父类:解决IoC循环依赖问题
这确实是IoC容器里典型的循环依赖场景——Context依赖ISomething,而Something又反过来依赖IContext,导致容器解析时陷入无限递归,最终抛出栈溢出异常。下面分享几种实用的最佳实践,你可以根据自己的代码结构和设计偏好选择:
方案1:用属性注入替代构造函数注入
Unity原生支持属性注入,通过延迟依赖的注入时机来打破循环。我们可以把Something中对IContext的构造依赖改成属性依赖:
public class Something : ISomething { // 用[Dependency]标记需要容器注入的属性 [Dependency] public IContext Wrapper { get; set; } // 提供无参构造函数,让容器先创建实例 public Something() { } public void Do() { Console.WriteLine(Wrapper.GetCommonData()); } }
注册逻辑不需要改动,Unity会先创建Context实例(此时Something已被实例化但还没注入IContext),等Context构造完成后,再把它注入到Something的Wrapper属性中。
注意:属性注入的缺点是无法保证属性在使用前一定被注入,所以要避免在
Something的构造函数中调用依赖Wrapper的逻辑,确保在容器完成解析后再使用该类的方法。
方案2:通过工厂模式延迟子组件创建
这种方式能保持构造函数注入的不可变性(依赖只读),同时把Something的创建时机推迟到Context构造完成之后:
首先定义一个工厂接口和实现:
public interface ISomethingFactory { ISomething Create(IContext context); } public class SomethingFactory : ISomethingFactory { public ISomething Create(IContext context) { return new Something(context); } }
然后修改Context的构造逻辑,通过工厂创建Something:
public class Context : IContext { public ISomething SomethingProcessor { get; } public IAnother AnotherProcessor { get; } // 依赖工厂而非直接依赖ISomething public Context(ISomethingFactory somethingFactory, IAnother another) { this.AnotherProcessor = another; // 此时Context已构造完成,可以安全传递this给Something this.SomethingProcessor = somethingFactory.Create(this); } public string GetCommonData() { return this.AnotherProcessor.GetMyData(); } }
注册时加上工厂的映射:
container.RegisterType<ISomethingFactory, SomethingFactory>(); container.RegisterType<IAnother, Another>(); container.RegisterType<IContext, Context>(); var cxt = container.Resolve<IContext>(); // 不再有循环依赖
方案3:重构依赖关系(最推荐的长期方案)
循环依赖往往暗示着设计可以优化——Something其实不需要依赖整个IContext,它只需要GetCommonData这个功能。我们可以把这个功能抽象成独立的接口,拆解不必要的依赖:
- 定义功能接口:
public interface ICommonDataProvider { string GetCommonData(); }
- 实现这个接口(可以单独写类,也让
Another直接实现):
// 这里选择让Another直接实现,减少额外类 public class Another : IAnother, ICommonDataProvider { public string GetMyData() { return "Foo"; } public string GetCommonData() { return GetMyData(); } }
- 修改
Something依赖具体的功能接口:
public class Something : ISomething { private readonly ICommonDataProvider _dataProvider; // 现在依赖的是单一功能接口,而非整个Context public Something(ICommonDataProvider dataProvider) { _dataProvider = dataProvider; } public void Do() { Console.WriteLine(_dataProvider.GetCommonData()); } }
Context的依赖也可以简化:
public class Context : IContext { public ISomething SomethingProcessor { get; } public IAnother AnotherProcessor { get; } public Context(ISomething something, IAnother another) { this.SomethingProcessor = something; this.AnotherProcessor = another; } // 如果Context仍需要对外提供GetCommonData,可以委托给Another public string GetCommonData() { return this.AnotherProcessor.GetMyData(); } }
这样整个依赖链就变成了Context → ISomething、IAnother,Something → ICommonDataProvider,完全消除了循环依赖,同时也符合单一职责原则,让每个类的依赖更清晰。
内容的提问来源于stack exchange,提问作者SirKumbskull

