接口泛型策略模式与工厂策略分发器:从多态到可扩展业务架构
前言
在业务系统开发中,我们经常会遇到类似这样的场景:
- 不同日志类型走不同处理逻辑
- 不同支付方式走不同支付渠道
- 不同消息类型走不同消费者
- 不同订单状态走不同处理流程
- 不同租户、不同业务线有不同规则
如果代码一开始写成这样:
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> | ILogServiceFactory | ILogServiceDispatcher |
| 主要作用 | 强类型处理业务逻辑 | 根据 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 else或switch开始膨胀- 不同类型的数据需要不同处理器
- 后续经常新增业务类型
- 希望降低主流程与具体实现之间的耦合
典型场景包括:
支付方式:支付宝支付事件 / 微信支付事件 / 银联支付事件
日志类型:操作日志事件 / 审计日志事件 / 登录日志事件 / 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 方法
逐步演进为:
日志事件基类
↓
接口泛型策略
↓
多个强类型策略实现
↓
工厂统一选择
↓
分发器统一执行
↓
主流程稳定调用
最终带来的收益是:
- 代码更清晰
- 职责更单一
- 扩展更方便
- 修改风险更低
- 更符合开闭原则
- 策略入参更安全
- 调用入口更统一
- 更适合中大型业务系统长期演进
一句话总结:
接口泛型策略模式是用强类型多态拆业务,工厂策略模式是用工厂选策略,工厂策略分发器是把事件统一交给正确策略执行;一个负责实现,一个负责选择,一个负责调度。