0Pricing
C# Academy · 课时

工厂与选项模式

使用工厂委托、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 反馈 — 无需本地设置。

此课程中的所有课时

  1. DI 容器基础
  2. 服务生命周期:Transient、Scoped、Singleton
  3. 构造函数注入与接口
  4. 工厂与选项模式
← 返回 C# Academy