工厂模式与注入依赖的生命周期困境及优化方案问询
不得不说,你提的这个问题真的很典型——当对象的依赖混合了**共享服务(无所有权)和专属实例(有所有权)**时,Dispose的边界特别容易模糊,稍不注意就会踩坑。你那个用Func<IQux>的方案虽然能解决问题,但确实存在构造函数执行复杂逻辑、懒加载风险的问题,完全可以换更优雅的方式。
下面几个方案供你参考:
方案1:用所有权包装器明确边界
核心思路是给依赖加一层“标记”,让Foo能清晰区分哪些是自己需要负责释放的。我们可以创建一个极简的Owned<T>包装类:
public sealed class Owned<T> : IDisposable where T : IDisposable { public T Value { get; } public Owned(T value) => Value = value ?? throw new ArgumentNullException(nameof(value)); public void Dispose() => Value.Dispose(); }
然后修改工厂和Foo的实现:
// 工厂负责包装专属依赖,明确标记这是Foo需要接管的 class FooFactory : IFooFactory { private readonly IBarService _barService; public FooFactory(IBarService barService) => _barService = barService; public IFoo Create() { var qux = new Qux(); return new Foo(_barService, new Owned<IQux>(qux)); } } class Foo : IFoo { private readonly IBarService _barService; private readonly Owned<IQux> _ownedQux; public Foo(IBarService barService, Owned<IQux> ownedQux) { _barService = barService; _ownedQux = ownedQux; } public void Dispose() { // 只释放标记为Owned的对象,边界一目了然 _ownedQux.Dispose(); } }
这个方案的优势:
- 可读性拉满,从类型就能直接看出依赖的所有权关系
- 构造函数只做赋值,没有复杂逻辑,完全符合快速失败原则
- 彻底避免误释放共享服务的风险
方案2:让DI容器接管生命周期(如果项目用容器的话)
如果你的项目已经在用依赖注入容器(比如Autofac、Microsoft.Extensions.DependencyInjection),那直接利用容器的生命周期管理能力是最省心的选择。
以Autofac为例,它原生支持Owned<T>,专门用来处理“对象拥有其依赖所有权”的场景:
// 注册IQux为瞬态(每次创建新实例) builder.RegisterType<Qux>().As<IQux>().InstancePerDependency(); // 注册FooFactory,注入IBarService和容器上下文 builder.RegisterType<FooFactory>().As<IFooFactory>(); // 工厂通过容器解析Owned<IQux> class FooFactory : IFooFactory { private readonly IBarService _barService; private readonly IComponentContext _context; public FooFactory(IBarService barService, IComponentContext context) { _barService = barService; _context = context; } public IFoo Create() { // 容器会跟踪这个Owned实例的生命周期 var ownedQux = _context.Resolve<Owned<IQux>>(); return new Foo(_barService, ownedQux); } } // Foo的Dispose逻辑和方案1一致,只释放Owned<IQux>
如果用MS DI,也可以通过创建临时范围来实现类似效果:在临时范围内解析IQux,把范围和Foo绑定,Foo Dispose时释放整个范围。
这个方案的优势:
- 把生命周期管理的复杂度交给容器,不用自己手动维护
- 支持更复杂的依赖链(比如Qux还有自己的依赖),容器会自动处理
- 完全符合依赖注入原则,工厂不需要硬编码
new Qux()
方案3:调整设计,让IQux的创建逻辑抽象化
你之前提到“将Qux的创建逻辑放入Foo会违背依赖注入原则”,其实不一定——如果把Qux的创建逻辑抽象成IQuxFactory,再注入给Foo,反而更符合DI的思想:
// 抽象Qux的工厂接口 interface IQuxFactory { IQux Create(); } // Qux的工厂实现 class QuxFactory : IQuxFactory { public IQux Create() => new Qux(); } // 修改Foo,注入IQuxFactory而不是直接注入IQux class Foo : IFoo { private readonly IBarService _barService; private readonly IQux _qux; public Foo(IBarService barService, IQuxFactory quxFactory) { _barService = barService; // 构造函数里创建Qux,逻辑简单,快速失败 _qux = quxFactory.Create(); } public void Dispose() => _qux.Dispose(); } // 原来的FooFactory现在只需要注入IBarService和IQuxFactory class FooFactory : IFooFactory { private readonly IBarService _barService; private readonly IQuxFactory _quxFactory; public FooFactory(IBarService barService, IQuxFactory quxFactory) { _barService = barService; _quxFactory = quxFactory; } public IFoo Create() => new Foo(_barService, _quxFactory); }
这个方案的优势:
- 完全遵循依赖注入原则,所有依赖都是注入的,没有硬编码实例化
- Foo的构造函数逻辑极简单,只是调用工厂创建Qux,创建失败会直接抛出异常(快速失败)
- 所有权关系依然清晰:Foo通过工厂创建的Qux,所以Foo负责Dispose
方案对比和建议
- 如果项目已经在用DI容器,方案2是最优解,省心又规范
- 如果不用容器,方案1的所有权包装器是最清晰的,没有额外复杂度
- 方案3适合需要严格遵循DI原则的场景,把Qux的创建逻辑也抽象出去,扩展性更好
你原来的Func<IQux>方案其实是方案3的简化版,但缺点是没有抽象成明确的工厂接口,可读性稍差。如果把Func<IQux>换成IQuxFactory,就能解决你担心的构造函数逻辑问题——因为工厂接口的Create()方法应该是轻量的(如果有复杂逻辑,那应该是工厂的责任,而不是Foo的),而且能保证快速失败。
另外,关于你担心的异步逻辑:如果Qux的创建是异步的,那确实不应该在构造函数里调用。这种情况下,可以把Foo的创建改成异步工厂方法,或者用异步初始化模式(比如IAsyncInitializable),但这就是另一个话题了。
内容的提问来源于stack exchange,提问作者Serge Semenov

