工厂与选项模式
使用工厂委托、IServiceProvider 和选项模式处理条件性或可配置的依赖。
工厂与选项模式 是 CoddyKit 上的免费 C# Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。
构造函数注入不足时
有时您需要根据运行时数据有条件地创建服务,或针对不同用例以不同方式配置服务。这正是工厂模式和选项模式发挥作用的地方。
使用 AddTransient 的工厂委托
您可以向 AddTransient、AddScoped 或 AddSingleton 传入工厂委托。该委托会接收 IServiceProvider,因此您可以解析其他服务。
builder.Services.AddTransient<IPaymentGateway>(sp =>
{
var config = sp.GetRequiredService<IOptions<PaymentConfig>>().Value;
return config.Provider == "stripe"
? new StripeGateway(config.ApiKey)
: new PayPalGateway(config.ClientId, config.Secret);
});使用 IOptionsSnapshot 的命名选项
选项模式会将配置节绑定到强类型类。单例服务使用 IOptions<T>,而需要按请求刷新值时使用 IOptionsSnapshot<T>。
public class SmtpSettings
{
public string Host { get; set; } = "";
public int Port { get; set; } = 587;
public string Username { get; set; } = "";
}
// Registration
builder.Services.Configure<SmtpSettings>(
builder.Configuration.GetSection("Smtp"));
// Consumption
public class EmailService
{
private readonly SmtpSettings _settings;
public EmailService(IOptions<SmtpSettings> opts)
=> _settings = opts.Value;
}使用 IOptionsMonitor 实现热重载
IOptionsMonitor<T> 会在访问时提供当前选项值,并在配置发生变化时通知您——非常适合需要实时配置更新的单例服务。
public class FeatureFlagService
{
private readonly IOptionsMonitor<FeatureFlags> _monitor;
public FeatureFlagService(IOptionsMonitor<FeatureFlags> monitor)
=> _monitor = monitor;
public bool IsEnabled(string flag)
{
// Always reads the latest config value
return _monitor.CurrentValue.Flags.GetValueOrDefault(flag);
}
}命名选项
命名选项允许您注册同一类型的多个配置。请使用 Configure<T>(name, ...) 注册,并使用 IOptionsSnapshot<T>.Get(name) 解析。
builder.Services.Configure<S3Settings>("primary",
builder.Configuration.GetSection("S3:Primary"));
builder.Services.Configure<S3Settings>("backup",
builder.Configuration.GetSection("S3:Backup"));
public class S3Service
{
public S3Service(IOptionsSnapshot<S3Settings> opts)
{
var primary = opts.Get("primary");
var backup = opts.Get("backup");
}
}启动时验证选项
使用 ValidateDataAnnotations() 或自定义验证器,在应用程序启动前确保配置正确。结合 ValidateOnStart() 使用,可以实现快速失败。
public class SmtpSettings
{
[Required] public string Host { get; set; } = "";
[Range(1, 65535)] public int Port { get; set; } = 587;
}
builder.Services
.AddOptions<SmtpSettings>()
.Bind(builder.Configuration.GetSection("Smtp"))
.ValidateDataAnnotations()
.ValidateOnStart();抽象工厂模式
抽象工厂接口允许您将工厂注入服务中,将构造过程延迟到运行时数据可用之后,而无需直接依赖 IServiceProvider。
public interface IReportFactory
{
IReport Create(string reportType);
}
public class ReportFactory : IReportFactory
{
private readonly IServiceProvider _sp;
public ReportFactory(IServiceProvider sp) => _sp = sp;
public IReport Create(string reportType) => reportType switch
{
"pdf" => _sp.GetRequiredService<PdfReport>(),
"excel"=> _sp.GetRequiredService<ExcelReport>(),
_ => throw new ArgumentException("Unknown type")
};
}
builder.Services.AddTransient<IReportFactory, ReportFactory>();键控服务(.NET 8)
.NET 8 引入了键控服务:您可以使用不同的键注册多个实现,并通过 [FromKeyedServices] 或 GetKeyedService 解析正确的实现。
builder.Services.AddKeyedScoped<IPaymentGateway, StripeGateway>("stripe");
builder.Services.AddKeyedScoped<IPaymentGateway, PayPalGateway>("paypal");
// Resolve in a class:
public class CheckoutService(
[FromKeyedServices("stripe")] IPaymentGateway stripe,
[FromKeyedServices("paypal")] IPaymentGateway paypal) { }使用 PostConfigure 应用覆盖配置
PostConfigure 会在所有 Configure 调用之后运行。您可以使用它在测试中应用覆盖配置,或无论加载了什么配置都强制执行不变量。
// In integration tests: force test values after real config
builder.Services.PostConfigure<SmtpSettings>(opts =>
{
opts.Host = "smtp.test.local";
opts.Port = 25;
});IServiceCollection 扩展方法
将注册封装到扩展方法中,以保持 Program.cs 整洁,并使模块能够在不同项目之间复用。
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddPaymentServices(
this IServiceCollection services,
IConfiguration config)
{
services.Configure<PaymentConfig>(config.GetSection("Payment"));
services.AddScoped<IPaymentGateway, StripeGateway>();
services.AddScoped<PaymentService>();
return services;
}
}
// Usage in Program.cs:
builder.Services.AddPaymentServices(builder.Configuration);实际应用:多提供商通知
将工厂与命名选项结合使用,您可以根据数据库中存储的用户偏好,在运行时选择正确的通知提供商。
builder.Services.AddKeyedScoped<INotifier, EmailNotifier>("email");
builder.Services.AddKeyedScoped<INotifier, SmsNotifier>("sms");
builder.Services.AddKeyedScoped<INotifier, PushNotifier>("push");
public class NotificationService
{
private readonly IServiceProvider _sp;
public NotificationService(IServiceProvider sp) => _sp = sp;
public Task SendAsync(string channel, string message)
{
var notifier = _sp.GetRequiredKeyedService<INotifier>(channel);
return notifier.SendAsync(message);
}
}快速检查
哪个接口提供当前选项值 AND 在配置发生变化时发出通知,因此适合单例服务?
回顾:工厂与选项模式
要点总结:
Add*方法中的工厂委托支持有条件地或根据运行时配置创建服务- 选项模式将配置节绑定到强类型 POCO 类
IOptions= 静态值,IOptionsSnapshot= 每个请求刷新,IOptionsMonitor= 实时更新- 使用
ValidateDataAnnotations().ValidateOnStart()在启动时验证选项 - 键控服务(.NET 8)可以替代为命名实现而采用的工厂变通方案
常见问题解答
「工厂与选项模式」课时是免费的吗?
是的 — 「工厂与选项模式」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。
「工厂与选项模式」这节课中我会学到什么?
使用工厂委托、IServiceProvider 和选项模式处理条件性或可配置的依赖。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C# Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「工厂与选项模式」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C# Academy 课中编写并运行代码吗?
能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。