依赖注入:复杂单例实例化是否需封装为工厂类?
回答你的两个问题
1. 即便只实例化一次,仍然建议封装到Factory类里
你的理解里有个小误区:Factory模式的核心价值不止是多次创建实例,更重要的是封装复杂的对象初始化逻辑,让代码更清晰、可维护,同时提升可测试性。
看你现在的CreateMyService方法,已经包含了不少细节:HttpClient的多层装饰、子服务的依赖组装。如果把这些逻辑放在一个专门的MyServiceFactory类里,好处很明显:
- 职责单一:Factory类只负责创建
IMyService及其依赖,和其他业务逻辑解耦; - 可读性提升:其他地方调用时,只需要知道调用
factory.Create(...),不用关心内部怎么组装装饰器、怎么创建子服务; - 可维护性更好:未来如果要修改装饰器顺序、替换子服务的依赖,只需要修改Factory类,不用在原来的宿主类里找代码;
- 便于测试:测试时你可以很容易地替换Factory(比如返回Mock的
IMyService),或者在Factory里注入测试用的依赖,不用改动业务代码。
举个重构后的示例:
public class MyServiceFactory { public IMyService Create(IService service, ILoggerFactory logFactory, IHttpClient client, IShareRessource sharedRessource) { var decoratedClient = new Decorator3(new Decorator2(new Decorator1(client))); var subService1 = new SubService1(sharedRessource, logFactory.CreateLogger<SubService1>()); var subService2 = new SubService2(sharedRessource, logFactory.CreateLogger<SubService2>(), service); return new MyService(decoratedClient, subService1, subService2); } }
哪怕现在只创建一次,这种封装也能让代码更干净,而且如果未来需求变化需要多次创建(比如多租户场景),你不用大幅重构代码。
2. 不使用DI框架时,实例化这类复杂服务的常规位置
通常会把这类复杂依赖的创建放在**应用的组合根(Composition Root)**里——这是一个专门负责组装所有依赖的集中位置,一般是应用的启动入口附近:
- 在C#里,可能是
Program.cs的Main方法、或者Startup.cs(.NET Framework)/Program.cs的服务配置部分(.NET Core+); - 在Java里,可能是
main方法、或者专门的AppConfig类。
组合根的核心思想是把所有依赖创建逻辑集中到一个地方,避免依赖的初始化代码散落在业务类里,导致维护困难。你可以在这里调用Factory创建IMyService实例,然后通过构造函数注入的方式把它传递给需要的地方,或者放到一个简单的容器(比如自己实现的轻量级容器,注意不要滥用服务定位器反模式)里供其他组件获取。
举个C#的示例(.NET 6+):
var builder = WebApplication.CreateBuilder(args); // 注册基础依赖 builder.Services.AddSingleton<IService, ConcreteService>(); builder.Services.AddSingleton<IHttpClient, ConcreteHttpClient>(); builder.Services.AddSingleton<IShareRessource, ConcreteShareRessource>(); // 创建MyService实例 var factory = new MyServiceFactory(); var myService = factory.Create( builder.Services.BuildServiceProvider().GetRequiredService<IService>(), builder.Services.BuildServiceProvider().GetRequiredService<ILoggerFactory>(), builder.Services.BuildServiceProvider().GetRequiredService<IHttpClient>(), builder.Services.BuildServiceProvider().GetRequiredService<IShareRessource>() ); // 把单例的MyService注册到容器,供其他组件使用 builder.Services.AddSingleton<IMyService>(myService); var app = builder.Build(); // ... 后续配置
这样所有依赖的创建逻辑都集中在启动阶段,业务代码只需要通过构造函数接收IMyService,不用关心它是怎么来的。
内容的提问来源于stack exchange,提问作者corentinaltepe
相关产品推荐
相关产品推荐

