接口泛型策略模式与工厂策略分发器:从多态到可扩展业务架构


前言

在业务系统开发中,我们经常会遇到类似这样的场景:

  • 不同日志类型走不同处理逻辑
  • 不同支付方式走不同支付渠道
  • 不同消息类型走不同消费者
  • 不同订单状态走不同处理流程
  • 不同租户、不同业务线有不同规则

如果代码一开始写成这样:

if (type == "operation")
{
    // 操作日志处理
}
else if (type == "audit")
{
    // 审计日志处理
}
else if (type == "login")
{
    // 登录日志处理
}
else if (type == "api")
{
    // API 访问日志处理
}

短期看没问题,但随着业务增长,这种代码会越来越难维护。

这时候就需要引入几个常见的设计思想:

  • 接口泛型策略模式
  • 工厂策略模式
  • 工厂策略分发器

它们本质上都是为了解决一个问题:

把变化的业务逻辑拆出去,让主流程保持稳定,同时让每个策略拥有自己的强类型入参。


一、什么是接口泛型策略模式?

接口泛型策略模式,本质上就是利用 泛型接口 + 多态,把不同的业务处理逻辑封装成不同的策略类。

普通接口策略模式通常会这样定义:

public interface ILogService
{
    Task WriteAsync(LogEvent logEvent, CancellationToken cancellationToken = default);
}

这种方式虽然可以统一入口,但具体策略里如果要访问子类特有字段,通常需要强转:

if (logEvent is not OperationLogEvent operationLogEvent)
{
    throw new ArgumentException("日志事件类型不匹配", nameof(logEvent));
}

如果每种日志事件都有自己的字段,使用泛型接口会更合适:

public interface ILogService<in TLogEvent>
    where TLogEvent : LogEvent
{
    Task WriteAsync(TLogEvent logEvent, CancellationToken cancellationToken = default);
}

这里的 TLogEvent 表示当前策略只处理某一种日志事件。

例如:

OperationLogService  ->  只处理 OperationLogEvent
AuditLogService      ->  只处理 AuditLogEvent
LoginLogService      ->  只处理 LoginLogEvent
ApiAccessLogService  ->  只处理 ApiAccessLogEvent

这样具体策略类里就不需要手动强转,编译期就能保证类型正确。

一句话理解:

接口泛型策略模式,就是让每个策略只处理自己真正关心的强类型业务模型。


二、为什么要用接口泛型策略模式?

假设我们有一个日志系统,日志类型包括:

operation -> 操作日志
audit     -> 审计日志
login     -> 登录日志
api       -> API 访问日志

如果所有日志都用同一个 LogEvent 基类处理,代码可以统一,但会有一个问题:

子类独有字段无法直接访问。

例如操作日志有:

OperationType
BusinessType
BusinessId
UserId

审计日志有:

AuditAction
ResourceName
OperatorId

API 日志有:

Path
HttpMethod
StatusCode
ElapsedMilliseconds

这些字段并不完全一样。

如果用非泛型接口,具体策略里就经常需要写:

var operationLogEvent = (OperationLogEvent)logEvent;

或者:

if (logEvent is not OperationLogEvent operationLogEvent)
{
    throw new ArgumentException("日志事件类型不匹配");
}

这会让每个策略类里都有重复的类型判断代码。

使用泛型接口之后,具体策略可以直接接收自己的事件类型:

public sealed class OperationLogService : ILogService<OperationLogEvent>
{
    public Task WriteAsync(OperationLogEvent logEvent, CancellationToken cancellationToken = default)
    {
        Console.WriteLine(logEvent.OperationType);
        return Task.CompletedTask;
    }
}

这样代码更干净,也更安全。


三、定义日志事件模型

先定义一个日志事件基类,用于承载所有日志事件的公共字段:

public abstract record LogEvent
{
    public string MessageId { get; init; } = Guid.NewGuid().ToString();

    public abstract string LogType { get; }

    public string Message { get; init; } = string.Empty;

    public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
}

操作日志事件:

public sealed record OperationLogEvent : LogEvent
{
    public override string LogType => "operation";

    public string OperationType { get; init; } = string.Empty;

    public string? BusinessType { get; init; }

    public string? BusinessId { get; init; }

    public string? UserId { get; init; }
}

审计日志事件:

public sealed record AuditLogEvent : LogEvent
{
    public override string LogType => "audit";

    public string AuditAction { get; init; } = string.Empty;

    public string ResourceName { get; init; } = string.Empty;

    public string? OperatorId { get; init; }
}

登录日志事件:

public sealed record LoginLogEvent : LogEvent
{
    public override string LogType => "login";

    public string UserName { get; init; } = string.Empty;

    public string? IpAddress { get; init; }

    public bool IsSuccess { get; init; }

    public string? FailureReason { get; init; }
}

API 访问日志事件:

public sealed record ApiAccessLogEvent : LogEvent
{
    public override string LogType => "api";

    public string Path { get; init; } = string.Empty;

    public string HttpMethod { get; init; } = string.Empty;

    public int StatusCode { get; init; }

    public long ElapsedMilliseconds { get; init; }
}

这里每一种日志事件都有自己的强类型字段。


四、先定义非泛型入口接口

如果只定义泛型接口:

ILogService<OperationLogEvent>
ILogService<AuditLogEvent>
ILogService<LoginLogEvent>
ILogService<ApiAccessLogEvent>

虽然类型安全,但是它们的泛型参数不同,无法很方便地统一放到一个集合里做动态分发。

而在实际项目中,我们经常需要根据 LogType 动态选择策略。

例如:

var service = logServiceFactory.GetRequiredService(logEvent.LogType);

所以推荐先定义一个 非泛型入口接口

public interface ILogService
{
    string LogType { get; }

    Type EventType { get; }

    Task WriteAsync(LogEvent logEvent, CancellationToken cancellationToken = default);
}

这个接口的作用是:

  • 方便工厂统一收集所有日志策略
  • 方便通过 LogType 动态选择策略
  • 给分发器提供统一调用入口

可以理解为:

非泛型接口负责统一入口,泛型接口负责强类型处理。


五、定义接口泛型策略

在非泛型入口的基础上,再定义泛型接口:

public interface ILogService<in TLogEvent> : ILogService
    where TLogEvent : LogEvent
{
    Task WriteAsync(TLogEvent logEvent, CancellationToken cancellationToken = default);
}

这里的 in TLogEvent 表示逆变。

简单理解就是:

这个接口只消费 TLogEvent,不返回 TLogEvent

因为 WriteAsync 的参数是输入参数,所以可以使用 in

这个接口表达的语义很清楚:

ILogService<OperationLogEvent>  ->  专门处理 OperationLogEvent
ILogService<AuditLogEvent>      ->  专门处理 AuditLogEvent
ILogService<LoginLogEvent>      ->  专门处理 LoginLogEvent
ILogService<ApiAccessLogEvent>  ->  专门处理 ApiAccessLogEvent

六、定义泛型策略基类

如果让每个具体策略类同时实现非泛型接口和泛型接口,会比较麻烦。

所以可以定义一个泛型抽象基类,统一完成类型适配:

public abstract class LogServiceBase<TLogEvent> : ILogService<TLogEvent>
    where TLogEvent : LogEvent
{
    public abstract string LogType { get; }

    public Type EventType => typeof(TLogEvent);

    public Task WriteAsync(
        LogEvent logEvent,
        CancellationToken cancellationToken = default)
    {
        if (logEvent is not TLogEvent typedLogEvent)
        {
            throw new ArgumentException(
                $"日志事件类型不匹配,当前服务需要 {typeof(TLogEvent).Name},实际传入 {logEvent.GetType().Name}",
                nameof(logEvent));
        }

        return WriteAsync(typedLogEvent, cancellationToken);
    }

    public abstract Task WriteAsync(
        TLogEvent logEvent,
        CancellationToken cancellationToken = default);
}

这个基类的作用是:

  • 对外暴露统一的 ILogService
  • 对内保留强类型的 ILogService<TLogEvent>
  • 类型检查只在基类中出现一次
  • 具体策略类不需要手动强转

也就是说,类型转换这件事不再散落在每个策略类里,而是统一放在基类中处理。


七、实现具体日志策略

操作日志策略:

public sealed class OperationLogService : LogServiceBase<OperationLogEvent>
{
    public override string LogType => "operation";

    public override Task WriteAsync(
        OperationLogEvent logEvent,
        CancellationToken cancellationToken = default)
    {
        Console.WriteLine($"写入操作日志:{logEvent.OperationType}");
        Console.WriteLine($"业务类型:{logEvent.BusinessType}");
        Console.WriteLine($"业务ID:{logEvent.BusinessId}");
        Console.WriteLine($"操作人:{logEvent.UserId}");

        return Task.CompletedTask;
    }
}

审计日志策略:

public sealed class AuditLogService : LogServiceBase<AuditLogEvent>
{
    public override string LogType => "audit";

    public override Task WriteAsync(
        AuditLogEvent logEvent,
        CancellationToken cancellationToken = default)
    {
        Console.WriteLine($"写入审计日志:{logEvent.AuditAction}");
        Console.WriteLine($"资源名称:{logEvent.ResourceName}");
        Console.WriteLine($"操作人:{logEvent.OperatorId}");

        return Task.CompletedTask;
    }
}

登录日志策略:

public sealed class LoginLogService : LogServiceBase<LoginLogEvent>
{
    public override string LogType => "login";

    public override Task WriteAsync(
        LoginLogEvent logEvent,
        CancellationToken cancellationToken = default)
    {
        Console.WriteLine($"写入登录日志:{logEvent.UserName}");
        Console.WriteLine($"IP 地址:{logEvent.IpAddress}");
        Console.WriteLine($"是否成功:{logEvent.IsSuccess}");
        Console.WriteLine($"失败原因:{logEvent.FailureReason}");

        return Task.CompletedTask;
    }
}

API 访问日志策略:

public sealed class ApiAccessLogService : LogServiceBase<ApiAccessLogEvent>
{
    public override string LogType => "api";

    public override Task WriteAsync(
        ApiAccessLogEvent logEvent,
        CancellationToken cancellationToken = default)
    {
        Console.WriteLine($"写入 API 日志:{logEvent.HttpMethod} {logEvent.Path}");
        Console.WriteLine($"状态码:{logEvent.StatusCode}");
        Console.WriteLine($"耗时:{logEvent.ElapsedMilliseconds}ms");

        return Task.CompletedTask;
    }
}

现在具体策略类里拿到的都是强类型事件。

例如 OperationLogService 里面的参数就是:

OperationLogEvent logEvent

所以可以直接访问:

logEvent.OperationType
logEvent.BusinessType
logEvent.BusinessId
logEvent.UserId

不需要再写:

logEvent as OperationLogEvent

八、什么是工厂策略模式?

接口泛型策略模式解决了“不同策略怎么定义”的问题。

但还有一个问题:

调用方怎么根据类型找到对应的策略?

比如现在有多个日志策略:

OperationLogService
AuditLogService
LoginLogService
ApiAccessLogService

当业务传入一个 logType = "audit",系统怎么知道应该使用 AuditLogService

这时候就需要工厂。

工厂的职责是:

根据条件,找到对应的策略实现。

在这个日志场景里,就是:

operation -> OperationLogService
audit     -> AuditLogService
login     -> LoginLogService
api       -> ApiAccessLogService

一句话理解:

工厂策略模式负责“找谁做”。


九、实现日志策略工厂

先定义工厂接口:

public interface ILogServiceFactory
{
    ILogService GetRequiredService(string logType);
}

然后实现工厂:

public sealed class LogServiceFactory : ILogServiceFactory
{
    private readonly IReadOnlyDictionary<string, ILogService> _services;

    public LogServiceFactory(IEnumerable<ILogService> services)
    {
        _services = services.ToDictionary(
            x => x.LogType,
            x => x,
            StringComparer.OrdinalIgnoreCase);
    }

    public ILogService GetRequiredService(string logType)
    {
        if (string.IsNullOrWhiteSpace(logType))
        {
            throw new ArgumentException("日志类型不能为空", nameof(logType));
        }

        if (_services.TryGetValue(logType, out var service))
        {
            return service;
        }

        throw new NotSupportedException($"不支持的日志类型:{logType}");
    }
}

这个工厂会把所有 ILogService 实现类收集起来,并根据 LogType 建立映射关系。

例如:

operation -> OperationLogService
audit     -> AuditLogService
login     -> LoginLogService
api       -> ApiAccessLogService

调用方可以这样使用:

var service = logServiceFactory.GetRequiredService(logEvent.LogType);

await service.WriteAsync(logEvent, cancellationToken);

注意:

这里调用的是非泛型入口:

ILogService.WriteAsync(LogEvent logEvent)

真正进入具体策略时,会由 LogServiceBase<TLogEvent> 转成对应的强类型事件。


十、为什么还需要工厂策略分发器?

有了工厂之后,调用方已经可以通过 LogType 找到策略了。

但是如果每个地方都这样写:

var service = logServiceFactory.GetRequiredService(logEvent.LogType);

await service.WriteAsync(logEvent, cancellationToken);

会有几个问题:

  • 调用方仍然需要知道工厂的存在
  • 查找策略和执行策略这两步会散落在多个地方
  • 空值校验、异常处理、日志记录、埋点等逻辑无法统一
  • 业务处理器职责不够纯粹

所以可以再增加一个 工厂策略分发器

分发器的职责是:

接收事件,调用工厂找到策略,然后执行策略。

也就是说:

Factory      ->  只负责找策略
Dispatcher   ->  负责分发并执行策略

一句话理解:

工厂负责“找谁做”,分发器负责“安排它去做”。


十一、实现日志策略分发器

先定义分发器接口:

public interface ILogServiceDispatcher
{
    Task DispatchAsync(LogEvent logEvent, CancellationToken cancellationToken = default);
}

然后实现分发器:

public sealed class LogServiceDispatcher : ILogServiceDispatcher
{
    private readonly ILogServiceFactory _logServiceFactory;

    public LogServiceDispatcher(ILogServiceFactory logServiceFactory)
    {
        _logServiceFactory = logServiceFactory;
    }

    public Task DispatchAsync(
        LogEvent logEvent,
        CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(logEvent);

        var service = _logServiceFactory.GetRequiredService(logEvent.LogType);

        return service.WriteAsync(logEvent, cancellationToken);
    }
}

这样业务代码就不用关心工厂怎么找策略了,只需要把事件交给分发器:

await logServiceDispatcher.DispatchAsync(logEvent, cancellationToken);

分发器把“查找策略 + 执行策略”的流程封装起来,业务代码只需要负责产生事件。


十二、完整调用流程

统一处理器:

public sealed class LogEventHandler
{
    private readonly ILogServiceDispatcher _logServiceDispatcher;

    public LogEventHandler(ILogServiceDispatcher logServiceDispatcher)
    {
        _logServiceDispatcher = logServiceDispatcher;
    }

    public Task HandleAsync(
        LogEvent logEvent,
        CancellationToken cancellationToken = default)
    {
        return _logServiceDispatcher.DispatchAsync(logEvent, cancellationToken);
    }
}

调用操作日志:

var logEvent = new OperationLogEvent
{
    Message = "用户创建订单",
    OperationType = "CreateOrder",
    BusinessType = "Order",
    BusinessId = "10001",
    UserId = "admin"
};

await handler.HandleAsync(logEvent, cancellationToken);

调用审计日志:

var logEvent = new AuditLogEvent
{
    Message = "用户导出敏感数据",
    AuditAction = "ExportSensitiveData",
    ResourceName = "Visitor",
    OperatorId = "admin"
};

await handler.HandleAsync(logEvent, cancellationToken);

调用登录日志:

var logEvent = new LoginLogEvent
{
    Message = "用户登录成功",
    UserName = "admin",
    IpAddress = "127.0.0.1",
    IsSuccess = true
};

await handler.HandleAsync(logEvent, cancellationToken);

最终流程是:

LogEvent

LogEventHandler

ILogServiceDispatcher

ILogServiceFactory

根据 LogType 找到对应 ILogService

LogServiceBase<TLogEvent> 做类型校验

进入具体强类型策略

引入分发器之后,LogEventHandler 不再直接依赖工厂,而是依赖更高层的分发抽象。

这样职责会更清晰:

LogEventHandler          ->  负责接收日志事件
LogServiceDispatcher     ->  负责分发执行
LogServiceFactory        ->  负责查找策略
LogServiceBase<TEvent>   ->  负责类型适配
具体 LogService          ->  负责具体业务处理

十三、依赖注入注册

在 .NET 中,可以这样注册:

services.AddScoped<ILogService, OperationLogService>();
services.AddScoped<ILogService, AuditLogService>();
services.AddScoped<ILogService, LoginLogService>();
services.AddScoped<ILogService, ApiAccessLogService>();

services.AddScoped<ILogServiceFactory, LogServiceFactory>();
services.AddScoped<ILogServiceDispatcher, LogServiceDispatcher>();

如果有些地方想直接注入强类型服务,也可以额外注册泛型接口:

services.AddScoped<ILogService<OperationLogEvent>, OperationLogService>();
services.AddScoped<ILogService<AuditLogEvent>, AuditLogService>();
services.AddScoped<ILogService<LoginLogEvent>, LoginLogService>();
services.AddScoped<ILogService<ApiAccessLogEvent>, ApiAccessLogService>();

如果只通过分发器统一调用,通常注册非泛型入口就够了:

services.AddScoped<ILogService, OperationLogService>();
services.AddScoped<ILogService, AuditLogService>();
services.AddScoped<ILogService, LoginLogService>();
services.AddScoped<ILogService, ApiAccessLogService>();

services.AddScoped<ILogServiceFactory, LogServiceFactory>();
services.AddScoped<ILogServiceDispatcher, LogServiceDispatcher>();

如果项目中使用 Scrutor,也可以扫描注册非泛型入口:

services.Scan(scan => scan
    .FromAssemblyOf<ILogService>()
    .AddClasses(classes => classes.AssignableTo<ILogService>())
    .As<ILogService>()
    .WithScopedLifetime());

services.AddScoped<ILogServiceFactory, LogServiceFactory>();
services.AddScoped<ILogServiceDispatcher, LogServiceDispatcher>();

这样新增日志策略时,只需要新增一个策略类即可。


十四、接口泛型策略模式、工厂策略模式、工厂策略分发器的区别

对比项接口泛型策略模式工厂策略模式工厂策略分发器
关注点定义不同业务策略根据条件选择策略统一分发并执行策略
核心职责解决“怎么做”解决“找谁做”解决“怎么调度执行”
常见形式ILogService<TLogEvent>ILogServiceFactoryILogServiceDispatcher
主要作用强类型处理业务逻辑根据 LogType 找实现封装查找 + 执行流程
适合场景多种业务实现多策略动态选择统一入口、统一调度
是否必须存在是策略核心多策略动态选择时需要统一入口场景推荐使用

一句话总结:

接口泛型策略模式负责定义业务实现,工厂策略模式负责找到对应实现,工厂策略分发器负责把事件交给正确的实现执行。


十五、为什么不建议直接注入所有策略到业务代码?

有些代码会这样写:

public class LogHandler
{
    private readonly IEnumerable<ILogService> _services;

    public LogHandler(IEnumerable<ILogService> services)
    {
        _services = services;
    }

    public async Task HandleAsync(LogEvent logEvent)
    {
        var service = _services.FirstOrDefault(x => x.LogType == logEvent.LogType);

        if (service == null)
        {
            throw new NotSupportedException();
        }

        await service.WriteAsync(logEvent);
    }
}

这种写法也可以工作。

但是问题是:

  • 策略选择逻辑散落在业务代码中
  • 多个地方都需要选择策略时会重复代码
  • 不利于统一处理异常、默认策略、降级逻辑
  • 业务处理器职责不够纯粹

更推荐拆成两层:

LogServiceFactory      ->  负责选择策略
LogServiceDispatcher   ->  负责分发执行

调用方只需要:

await logServiceDispatcher.DispatchAsync(logEvent, cancellationToken);

这样业务处理器只负责业务流程,分发器负责调度执行,工厂负责策略选择。


十六、什么时候适合使用接口泛型策略模式?

适合以下场景:

  • 同一类业务有多种实现方式
  • 不同策略需要不同的强类型入参
  • 业务分支越来越多
  • if elseswitch 开始膨胀
  • 不同类型的数据需要不同处理器
  • 后续经常新增业务类型
  • 希望降低主流程与具体实现之间的耦合

典型场景包括:

支付方式:支付宝支付事件 / 微信支付事件 / 银联支付事件
日志类型:操作日志事件 / 审计日志事件 / 登录日志事件 / API 日志事件
消息处理:订单消息 / 用户消息 / 库存消息
导出方式:Excel 导出任务 / CSV 导出任务 / PDF 导出任务
通知方式:短信通知 / 邮件通知 / 站内信通知 / Webhook 通知

如果每种策略的参数模型完全一样,可以使用普通接口策略模式。

如果每种策略的参数模型不同,接口泛型策略模式更合适。


十七、什么时候需要工厂策略模式?

当系统中有多个策略实现,并且需要根据某个条件动态选择时,就适合使用工厂策略模式。

例如:

根据 LogType 选择日志服务
根据 PayType 选择支付服务
根据 MessageType 选择消息处理器
根据 ExportType 选择导出服务
根据 TenantCode 选择租户规则

如果只有一个实现类,就没有必要引入工厂。

如果策略数量很多,并且选择逻辑可能变化,就非常适合使用工厂。


十八、什么时候需要工厂策略分发器?

当系统中存在统一入口,并且你不希望业务代码直接接触工厂时,就适合引入分发器。

例如:

统一日志入口
MQ 消息统一消费入口
订单状态流转入口
支付回调统一入口
导出任务统一入口
通知发送统一入口

分发器适合封装这些流程:

参数校验
策略选择
策略执行
异常处理
日志记录
埋点统计
失败降级

也就是说,如果你的业务代码经常出现:

var service = factory.GetRequiredService(type);

await service.HandleAsync(message);

就可以考虑封装成:

await dispatcher.DispatchAsync(message);

这样主流程会更干净。


十九、接口泛型策略模式是不是就是接口多态?

可以这么理解,但它比普通接口多态更进一步。

普通接口多态:

ILogService service = new OperationLogService();

await service.WriteAsync(logEvent);

接口泛型策略模式:

ILogService<OperationLogEvent> service = new OperationLogService();

await service.WriteAsync(operationLogEvent);

区别在于:

普通接口多态      ->  统一入口,参数通常是基类
接口泛型策略模式  ->  统一规则,同时保留具体参数类型

接口多态是语言能力。

策略模式是架构思想。

泛型接口是类型安全增强。

也就是说:

接口多态是实现手段,策略模式是业务解耦方案,泛型接口让策略参数更安全。


二十、项目中的推荐目录结构

以日志模块为例,可以这样组织代码:

Infrastructure
└─ Logging
   ├─ Abstractions
   │  ├─ ILogService.cs
   │  ├─ ILogService{TLogEvent}.cs
   │  ├─ ILogServiceFactory.cs
   │  └─ ILogServiceDispatcher.cs

   ├─ Events
   │  ├─ LogEvent.cs
   │  ├─ OperationLogEvent.cs
   │  ├─ AuditLogEvent.cs
   │  ├─ LoginLogEvent.cs
   │  └─ ApiAccessLogEvent.cs

   ├─ Services
   │  ├─ LogServiceBase.cs
   │  ├─ OperationLogService.cs
   │  ├─ AuditLogService.cs
   │  ├─ LoginLogService.cs
   │  └─ ApiAccessLogService.cs

   ├─ Factories
   │  └─ LogServiceFactory.cs

   ├─ Dispatchers
   │  └─ LogServiceDispatcher.cs

   └─ DependencyInjection.cs

如果是更严格的 DDD 项目,也可以把抽象接口放到 Domain 或 Application 层,把具体实现放到 Infrastructure 层。

但在轻量 DDD 或基础设施型日志组件中,把日志抽象和实现都放在 Infrastructure.Logging 下也是可以接受的。

关键是保持边界清晰:

Abstractions  -> 放接口
Events        -> 放日志事件模型
Services      -> 放具体策略实现和泛型基类
Factories     -> 放策略选择逻辑
Dispatchers   -> 放统一分发逻辑

二十一、最终核心代码

LogEvent

public abstract record LogEvent
{
    public string MessageId { get; init; } = Guid.NewGuid().ToString();

    public abstract string LogType { get; }

    public string Message { get; init; } = string.Empty;

    public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
}

ILogService

public interface ILogService
{
    string LogType { get; }

    Type EventType { get; }

    Task WriteAsync(LogEvent logEvent, CancellationToken cancellationToken = default);
}

ILogService<TLogEvent>

public interface ILogService<in TLogEvent> : ILogService
    where TLogEvent : LogEvent
{
    Task WriteAsync(TLogEvent logEvent, CancellationToken cancellationToken = default);
}

LogServiceBase<TLogEvent>

public abstract class LogServiceBase<TLogEvent> : ILogService<TLogEvent>
    where TLogEvent : LogEvent
{
    public abstract string LogType { get; }

    public Type EventType => typeof(TLogEvent);

    public Task WriteAsync(
        LogEvent logEvent,
        CancellationToken cancellationToken = default)
    {
        if (logEvent is not TLogEvent typedLogEvent)
        {
            throw new ArgumentException(
                $"日志事件类型不匹配,当前服务需要 {typeof(TLogEvent).Name},实际传入 {logEvent.GetType().Name}",
                nameof(logEvent));
        }

        return WriteAsync(typedLogEvent, cancellationToken);
    }

    public abstract Task WriteAsync(
        TLogEvent logEvent,
        CancellationToken cancellationToken = default);
}

ILogServiceFactory

public interface ILogServiceFactory
{
    ILogService GetRequiredService(string logType);
}

LogServiceFactory

public sealed class LogServiceFactory : ILogServiceFactory
{
    private readonly IReadOnlyDictionary<string, ILogService> _services;

    public LogServiceFactory(IEnumerable<ILogService> services)
    {
        _services = services.ToDictionary(
            x => x.LogType,
            x => x,
            StringComparer.OrdinalIgnoreCase);
    }

    public ILogService GetRequiredService(string logType)
    {
        if (string.IsNullOrWhiteSpace(logType))
        {
            throw new ArgumentException("日志类型不能为空", nameof(logType));
        }

        if (_services.TryGetValue(logType, out var service))
        {
            return service;
        }

        throw new NotSupportedException($"不支持的日志类型:{logType}");
    }
}

ILogServiceDispatcher

public interface ILogServiceDispatcher
{
    Task DispatchAsync(LogEvent logEvent, CancellationToken cancellationToken = default);
}

LogServiceDispatcher

public sealed class LogServiceDispatcher : ILogServiceDispatcher
{
    private readonly ILogServiceFactory _logServiceFactory;

    public LogServiceDispatcher(ILogServiceFactory logServiceFactory)
    {
        _logServiceFactory = logServiceFactory;
    }

    public Task DispatchAsync(
        LogEvent logEvent,
        CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(logEvent);

        var service = _logServiceFactory.GetRequiredService(logEvent.LogType);

        return service.WriteAsync(logEvent, cancellationToken);
    }
}

OperationLogEvent

public sealed record OperationLogEvent : LogEvent
{
    public override string LogType => "operation";

    public string OperationType { get; init; } = string.Empty;

    public string? BusinessType { get; init; }

    public string? BusinessId { get; init; }

    public string? UserId { get; init; }
}

OperationLogService

public sealed class OperationLogService : LogServiceBase<OperationLogEvent>
{
    public override string LogType => "operation";

    public override Task WriteAsync(
        OperationLogEvent logEvent,
        CancellationToken cancellationToken = default)
    {
        Console.WriteLine($"写入操作日志:{logEvent.OperationType}");
        Console.WriteLine($"业务类型:{logEvent.BusinessType}");
        Console.WriteLine($"业务ID:{logEvent.BusinessId}");
        Console.WriteLine($"操作人:{logEvent.UserId}");

        return Task.CompletedTask;
    }
}

AuditLogEvent

public sealed record AuditLogEvent : LogEvent
{
    public override string LogType => "audit";

    public string AuditAction { get; init; } = string.Empty;

    public string ResourceName { get; init; } = string.Empty;

    public string? OperatorId { get; init; }
}

AuditLogService

public sealed class AuditLogService : LogServiceBase<AuditLogEvent>
{
    public override string LogType => "audit";

    public override Task WriteAsync(
        AuditLogEvent logEvent,
        CancellationToken cancellationToken = default)
    {
        Console.WriteLine($"写入审计日志:{logEvent.AuditAction}");
        Console.WriteLine($"资源名称:{logEvent.ResourceName}");
        Console.WriteLine($"操作人:{logEvent.OperatorId}");

        return Task.CompletedTask;
    }
}

DependencyInjection

public static class DependencyInjection
{
    public static IServiceCollection AddLoggingServices(this IServiceCollection services)
    {
        services.AddScoped<ILogService, OperationLogService>();
        services.AddScoped<ILogService, AuditLogService>();
        services.AddScoped<ILogService, LoginLogService>();
        services.AddScoped<ILogService, ApiAccessLogService>();

        services.AddScoped<ILogServiceFactory, LogServiceFactory>();
        services.AddScoped<ILogServiceDispatcher, LogServiceDispatcher>();

        return services;
    }
}

LogEventHandler

public sealed class LogEventHandler
{
    private readonly ILogServiceDispatcher _logServiceDispatcher;

    public LogEventHandler(ILogServiceDispatcher logServiceDispatcher)
    {
        _logServiceDispatcher = logServiceDispatcher;
    }

    public Task HandleAsync(
        LogEvent logEvent,
        CancellationToken cancellationToken = default)
    {
        return _logServiceDispatcher.DispatchAsync(logEvent, cancellationToken);
    }
}

二十二、总结

接口泛型策略模式、工厂策略模式和工厂策略分发器经常一起出现。

接口泛型策略模式解决的是:

不同业务逻辑如何拆分成不同实现,并让每个实现拥有自己的强类型参数。

工厂策略模式解决的是:

根据业务条件如何选择正确的实现。

工厂策略分发器解决的是:

如何把事件统一交给正确的策略执行。

在实际项目中,三者组合起来,可以让代码从:

一个大 if else 方法

逐步演进为:

日志事件基类

接口泛型策略

多个强类型策略实现

工厂统一选择

分发器统一执行

主流程稳定调用

最终带来的收益是:

  • 代码更清晰
  • 职责更单一
  • 扩展更方便
  • 修改风险更低
  • 更符合开闭原则
  • 策略入参更安全
  • 调用入口更统一
  • 更适合中大型业务系统长期演进

一句话总结:

接口泛型策略模式是用强类型多态拆业务,工厂策略模式是用工厂选策略,工厂策略分发器是把事件统一交给正确策略执行;一个负责实现,一个负责选择,一个负责调度。

微信订阅号二维码