第一章:告别硬编码依赖:ASP.NET Core工厂模式如何让你的代码瞬间变“智能”
在现代软件开发中,硬编码依赖会导致系统耦合度高、难以测试和维护。ASP.NET Core 提供了强大的依赖注入(DI)机制,而工厂模式进一步增强了这一能力,使对象创建过程更加灵活与可控。
为何需要工厂模式
直接通过构造函数注入服务适用于大多数场景,但当对象的创建依赖于运行时数据或需要根据条件返回不同实现时,传统 DI 容器便显得力不从心。工厂模式正是为了解决这类动态创建问题而生。
- 解耦对象创建逻辑与使用逻辑
- 支持基于上下文选择具体实现
- 提升代码可测试性与扩展性
实现一个简单的服务工厂
定义一个接口及多个实现,再通过工厂类决定实例化哪一个:
// 服务接口
public interface INotificationService
{
void Send(string message);
}
// 邮件实现
public class EmailNotificationService : INotificationService
{
public void Send(string message) => Console.WriteLine($"邮件发送: {message}");
}
// 短信实现
public class SmsNotificationService : INotificationService
{
public void Send(string message) => Console.WriteLine($"短信发送: {message}");
}
接着注册工厂服务:
// 工厂定义
public interface INotificationServiceFactory
{
INotificationService CreateService(string type);
}
public class NotificationServiceFactory : INotificationServiceFactory
{
public INotificationService CreateService(string type)
{
return type.ToLower() switch
{
"email" => new EmailNotificationService(),
"sms" => new SmsNotificationService(),
_ => throw new ArgumentException("不支持的通知类型")
};
}
}
在
Program.cs 中注册:
builder.Services.AddSingleton<INotificationServiceFactory, NotificationServiceFactory>();
运行时动态选择服务
控制器中可通过工厂获取对应服务:
| 输入类型 | 实际调用服务 |
|---|
| email | EmailNotificationService |
| sms | SmsNotificationService |
graph LR
A[客户端请求] --> B{工厂判断类型}
B -->|email| C[EmailService]
B -->|sms| D[SmsService]
C --> E[发送通知]
D --> E
第二章:深入理解ASP.NET Core中的工厂模式
2.1 工厂模式的核心思想与设计动机
解耦对象创建与使用
工厂模式的核心在于将对象的实例化过程从客户端代码中剥离,交由专门的“工厂”来统一管理。这种方式有效降低了系统模块间的耦合度,使代码更易于维护和扩展。
典型应用场景
当系统需要根据不同的条件创建不同类型的对象时,直接在代码中使用
new 操作会导致逻辑分散且难以扩展。工厂模式通过封装创建逻辑,提供统一接口获取实例。
public interface Product {
void use();
}
public class ConcreteProductA implements Product {
public void use() {
System.out.println("Using Product A");
}
}
上述接口定义了产品规范,具体实现类由工厂决定何时实例化。客户端仅依赖抽象,无需了解具体类型。
- 提升可维护性:新增产品无需修改原有代码
- 符合开闭原则:对扩展开放,对修改关闭
- 集中管理对象生命周期
2.2 .NET中工厂模式的典型应用场景
在.NET开发中,工厂模式广泛应用于需要动态创建对象的场景,尤其适用于解耦对象的创建与使用。
数据库访问层的抽象
通过工厂模式可屏蔽不同数据库(如SQL Server、MySQL)之间的实现差异:
public interface IDbConnectionProvider
{
DbConnection CreateConnection();
}
public class SqlServerProvider : IDbConnectionProvider
{
public DbConnection CreateConnection() =>
new SqlConnection("server=.;database=test");
}
上述代码中,
CreateConnection 方法封装了具体连接对象的实例化逻辑,调用方无需关心底层数据库类型。
依赖注入中的服务注册
结合ASP.NET Core的DI容器,工厂可用于按需生成服务实例:
- 避免单例模式下状态污染
- 支持基于配置或上下文选择实现类
- 提升系统扩展性与测试友好性
2.3 IServiceProvider与依赖解析的底层机制
服务提供者的角色与生命周期
`IServiceProvider` 是 .NET 依赖注入系统的核心接口,负责在运行时解析已注册的服务实例。它基于 `IServiceCollection` 中配置的服务生命周期(Singleton、Scoped、Transient)来决定实例的创建与复用策略。
- Singleton:在整个应用程序生命周期内共享同一实例;
- Scoped:在每个请求或作用域内共享实例;
- Transient:每次请求都创建新实例。
依赖解析流程
当调用 `GetService()` 时,`IServiceProvider` 会递归解析目标类型的构造函数参数,逐层构建依赖树。
public class OrderService
{
private readonly IPaymentProcessor _processor;
private readonly ILogger _logger;
// 构造函数注入由 IServiceProvider 自动解析
public OrderService(IPaymentProcessor processor, ILogger logger)
{
_processor = processor;
_logger = logger;
}
}
上述代码中,`IServiceProvider` 在实例化 `OrderService` 时,自动查找并注入 `IPaymentProcessor` 和 `ILogger` 的实现。若任一依赖未注册,将返回 null 或抛出异常,具体行为取决于解析上下文配置。
2.4 简单工厂 vs 工厂方法 vs 抽象工厂在DI中的实践选择
在依赖注入(DI)框架设计中,对象创建策略直接影响系统的扩展性与维护成本。不同工厂模式适用于不同层级的解耦需求。
简单工厂:快速原型的理想选择
适合固定类型映射场景,通过配置直接返回实例。
func NewService(serviceType string) Service {
switch serviceType {
case "email": return &EmailService{}
case "sms": return &SMSService{}
default: panic("unknown type")
}
}
该实现便于快速搭建原型,但违反开闭原则,新增类型需修改源码。
工厂方法与抽象工厂:面向扩展的进阶方案
工厂方法将创建逻辑下放至子类,支持动态扩展;抽象工厂则聚焦产品族管理,适用于多维度变体场景。在 DI 容器中注册时,推荐使用抽象工厂统一管理数据库、缓存等配套组件组。
| 模式 | 可扩展性 | 适用场景 |
|---|
| 简单工厂 | 低 | 固定类型创建 |
| 工厂方法 | 中 | 单一产品继承体系 |
| 抽象工厂 | 高 | 多产品族协同创建 |
2.5 工厂模式如何解耦服务创建与业务逻辑
工厂模式通过将对象的创建过程封装在独立的工厂类中,使业务逻辑不再直接依赖具体的服务实现。这种方式实现了创建逻辑与使用逻辑的分离,提升了代码的可维护性与扩展性。
典型实现示例
type Service interface {
Process() string
}
type PaymentService struct{}
func (p *PaymentService) Process() string {
return "Processing payment"
}
type NotificationService struct{}
func (n *NotificationService) Process() string {
return "Sending notification"
}
type ServiceFactory struct{}
func (f *ServiceFactory) CreateService(serviceType string) Service {
switch serviceType {
case "payment":
return &PaymentService{}
case "notification":
return ¬ificationService{}
default:
panic("Unknown service type")
}
}
上述代码中,
CreateService 方法根据输入参数返回对应的
Service 实现,业务代码无需感知具体类型的构造细节。
优势分析
- 新增服务时只需扩展工厂逻辑,符合开闭原则
- 业务模块仅依赖抽象接口,降低耦合度
- 便于单元测试中注入模拟对象
第三章:ASP.NET Core内置工厂支持详解
3.1 使用Func<T>实现轻量级服务工厂
在依赖注入广泛应用的现代应用架构中,有时需要延迟创建服务实例。`Func` 提供了一种简洁的方式,作为工厂委托动态生成对象。
基本用法
public interface IService { }
public class ServiceA : IService { }
// 容器注册
services.AddTransient<IService, ServiceA>();
// 自动支持 Func<IService> 注入
当类型 `Func` 被注入时,DI 容器会自动提供一个委托,调用时返回新实例。
适用场景与优势
- 避免服务过早初始化,提升启动性能
- 在作用域内按需创建多个实例
- 减少对 IServiceProvider 的直接依赖
该机制适用于事件处理、策略选择等需运行时决定实例化逻辑的场景。
3.2 ILogger<T>背后的工厂机制剖析
在 .NET 日志系统中,`ILogger` 并非直接提供日志功能,而是通过 `ILoggerFactory` 动态创建的实例。该机制实现了日志器的延迟初始化与类型隔离。
工厂模式的核心作用
`ILoggerFactory` 负责创建和管理 `ILogger` 实例,确保每个类型 `T` 对应唯一的日志器名称,通常为 `T` 的全名。这种设计支持多租户、多级别日志输出。
public class MyService
{
private readonly ILogger _logger;
public MyService(ILogger logger)
{
_logger = logger;
}
}
上述代码中,依赖注入容器会解析 `ILogger<MyService>`,并由 `LoggerFactory` 生成对应实例。参数 `_logger` 自动绑定命名空间与类名作为日志来源标识。
内部创建流程
- 请求 `ILogger` 时,框架提取 `T` 的类型名称作为日志类别
- 通过工厂查找或新建对应配置的日志提供程序(Provider)
- 组合多个日志级别与过滤规则,返回线程安全的日志器实例
3.3 如何利用IHttpClientFactory构建弹性HTTP客户端
在现代微服务架构中,频繁的HTTP调用容易引发资源耗尽与连接泄漏。`IHttpClientFactory` 提供了统一的客户端管理机制,有效复用 `HttpClient` 实例,避免常见陷阱。
基本使用方式
通过依赖注入注册强类型客户端:
services.AddHttpClient<IApiClient, ApiClient>(client =>
{
client.BaseAddress = new Uri("https://api.example.com");
client.Timeout = TimeSpan.FromSeconds(10);
});
该配置创建命名化客户端实例,生命周期由框架托管,确保底层 `HttpMessageHandler` 的高效复用。
集成弹性策略
结合 Polly 可定义重试、熔断等容错策略:
- 重试最多3次,间隔呈指数增长
- 请求失败率超过阈值时触发熔断
- 支持超时与降级逻辑组合
此模式显著提升系统在不稳定性网络环境下的鲁棒性。
第四章:自定义工厂注入提升代码灵活性
4.1 定义接口驱动的工厂契约
在现代软件架构中,接口驱动的设计模式提升了模块间的解耦与可测试性。通过定义清晰的工厂契约,系统可在运行时动态选择具体实现。
工厂接口设计
type ServiceFactory interface {
CreateService(serviceType string) (Service, error)
}
该接口声明了一个通用的创建方法,参数
serviceType 指定所需服务类型,返回对应的业务服务实例。通过此契约,调用方无需感知对象的构造细节。
实现类注册机制
- 基于映射(map)注册具体类型
- 支持运行时动态扩展新实现
- 结合依赖注入容器提升管理能力
4.2 实现支持运行时参数的服务工厂
在现代微服务架构中,服务的初始化往往依赖动态配置。通过实现支持运行时参数的服务工厂,可以在不重启应用的前提下灵活调整服务行为。
工厂接口设计
定义通用工厂接口,支持传入运行时参数并返回对应实例:
type ServiceFactory interface {
Create(params map[string]interface{}) (Service, error)
}
该接口允许在创建服务时注入环境相关参数,如数据库连接串、超时阈值等。
参数映射表
常用运行时参数及其用途如下:
| 参数名 | 类型 | 说明 |
|---|
| timeout | int | 设置服务调用超时时间(毫秒) |
| retry_count | int | 重试次数 |
4.3 结合策略模式与工厂模式动态选择服务实例
在微服务架构中,面对多变的业务场景,需动态选择最适合的服务实现。通过融合策略模式定义统一行为接口,再由工厂模式按上下文条件创建具体策略实例,可实现高度解耦。
核心接口设计
// ServiceStrategy 定义服务执行策略
type ServiceStrategy interface {
Execute(data string) string
}
该接口抽象了不同服务的调用方式,所有具体实现均遵循此契约。
工厂创建实例
- 根据请求类型(如支付、通知)判断策略类型
- 工厂返回对应服务实例,避免调用方感知创建逻辑
func NewService(type string) ServiceStrategy {
switch type {
case "payment":
return &PaymentService{}
case "notification":
return ¬ificationService{}
default:
panic("unknown type")
}
}
工厂封装实例化过程,提升可维护性与扩展性。
4.4 工厂注册与生命周期管理最佳实践
工厂注册的模块化设计
为提升可维护性,建议将工厂注册逻辑封装在独立初始化函数中。通过依赖注入容器统一管理实例创建与绑定。
func RegisterServices(container *DIContainer) {
container.Register("database", NewDatabaseConnection)
container.Register("cache", NewRedisClient)
}
上述代码将服务构造函数注册至容器,延迟实例化至首次调用,降低启动开销。参数为服务名称与对应创建函数,支持运行时动态解析。
生命周期策略配置
根据对象使用频率选择合适的生命周期模式:
- Singleton:全局唯一实例,适用于数据库连接池
- Scoped:请求级生命周期,用于事务上下文
- Transient:每次获取均创建新实例,适合轻量无状态服务
第五章:从工厂模式看现代ASP.NET Core架构演进
工厂模式在依赖注入中的角色演变
早期 ASP.NET 应用中,开发者常手动实现工厂类来创建服务实例。而在 ASP.NET Core 中,内置的依赖注入容器与工厂模式深度集成,使得服务解析更加灵活。例如,通过
IServiceProvider 获取服务时,框架内部即采用工厂机制动态创建实例。
- 支持条件化服务注册,如根据环境配置返回不同实现
- 允许延迟初始化,提升应用启动性能
- 便于单元测试中替换模拟对象
使用工厂方法注册服务
在
Program.cs 中,可通过工厂委托注册复杂初始化逻辑的服务:
builder.Services.AddSingleton<IEmailService>(provider =>
{
var config = provider.GetRequiredService<IConfiguration>();
var environment = builder.Environment;
return environment.IsDevelopment()
? new FakeEmailService()
: new SmtpEmailService(config["Smtp:Server"]);
});
对比传统与现代实现方式
| 特性 | 传统工厂模式 | ASP.NET Core 工厂注册 |
|---|
| 生命周期管理 | 手动控制 | 由 DI 容器自动管理 |
| 可测试性 | 依赖抽象接口 | 天然支持 Mock 注入 |
| 配置灵活性 | 硬编码分支逻辑 | 结合 IConfiguration 动态决策 |
实际应用场景:多租户服务构建
在多租户系统中,可根据请求上下文动态选择数据访问实现:
builder.Services.AddScoped<ITenantService>(provider =>
{
var httpContextAccessor = provider.GetRequiredService<IHttpContextAccessor>();
var tenantId = httpContextAccessor.HttpContext?.Request.Headers["X-Tenant-Id"];
return tenantId switch
{
"A" => new TenantASpecificService(),
"B" => new TenantBSpecificService(),
_ => new DefaultTenantService()
};
});