如何重构过度传递的操作上下文(context)实例?
重构冗余的Action上下文传递方案
这种显式传递Context实例导致代码冗余的问题我之前也碰到过,结合你的场景——需要在日志等场景获取ActionBlockName,且同一个Action可能属于不同Block——给你几个实用的重构方案:
1. 使用线程/异步本地存储(AsyncLocal)
如果你的应用涉及多线程或异步操作,用AsyncLocal(以.NET为例)可以让上下文在当前执行流中自动传递,不用手动传参:
// 定义一个上下文持有者类 public static class ActionContextHolder { private static readonly AsyncLocal<ActionContext> _currentContext = new AsyncLocal<ActionContext>(); // 公开当前上下文 public static ActionContext Current => _currentContext.Value; // 设置上下文并返回可释放对象,用于恢复之前的上下文 public static IDisposable SetContext(ActionContext context) { var previousContext = _currentContext.Value; _currentContext.Value = context; return new Disposable(() => _currentContext.Value = previousContext); } // 内部辅助的可释放类 private class Disposable : IDisposable { private readonly Action _disposeAction; public Disposable(Action disposeAction) => _disposeAction = disposeAction; public void Dispose() => _disposeAction(); } }
使用方式非常简洁,用using块包裹执行逻辑,确保上下文在执行完成后恢复:
using (ActionContextHolder.SetContext(context)) { ExecuteAction(action); // 这里不用再传context了! }
然后在ExecuteAction内部直接获取上下文:
public void ExecuteAction(Action action) { var logString = $"action {action.Name} was executed as part of {ActionContextHolder.Current.ActionBlockName} action block"; // 执行action逻辑... }
2. 依赖注入(DI)注入上下文
如果你的应用已经使用了依赖注入容器,可以把ActionContext注册为**作用域(Scoped)**服务,这样在需要的地方自动注入,避免手动传递:
注册上下文服务
// 以.NET的DI为例,注册为Scoped,确保每个请求/作用域内上下文唯一 services.AddScoped<ActionContext>(sp => { // 根据当前场景创建或获取上下文实例 return new ActionContext { ActionBlockName = "YourBlockName" }; });
在执行器中注入上下文
public class ActionExecutor { private readonly ActionContext _context; // 通过构造函数注入上下文 public ActionExecutor(ActionContext context) { _context = context; } public void ExecuteAction(Action action) { var logString = $"action {action.Name} was executed as part of {_context.ActionBlockName} action block"; // 执行action逻辑... } }
使用时直接从DI容器获取ActionExecutor实例调用方法即可,不用手动传Context。
3. 将上下文与Action绑定(固定场景适用)
如果同一个Action实例始终属于同一个ActionBlock,可以在初始化Action时直接把上下文绑定到Action的属性上:
public class Action { public string Name { get; set; } // 添加上下文属性 public ActionContext Context { get; set; } }
然后ExecuteAction只需要接收Action:
public void ExecuteAction(Action action) { var logString = $"action {action.Name} was executed as part of {action.Context.ActionBlockName} action block"; // 执行action逻辑... }
⚠️ 注意:这个方案只适用于Action和ActionBlock是固定绑定的场景,如果同一个Action实例可能被用于不同的ActionBlock,就不要用这个方法。
4. 装饰器模式包装Action
用装饰器模式给Action添加上下文相关的逻辑(比如日志),同时保持Action本身的职责单一:
定义Action接口
public interface IAction { string Name { get; } void Execute(); } // 实现具体的Action public class ConcreteAction : IAction { public string Name { get; set; } public void Execute() { // 原始的Action执行逻辑 } }
实现上下文装饰器
public class ActionWithContextDecorator : IAction { private readonly IAction _innerAction; private readonly ActionContext _context; public ActionWithContextDecorator(IAction innerAction, ActionContext context) { _innerAction = innerAction; _context = context; } public string Name => _innerAction.Name; public void Execute() { // 先处理日志逻辑 var logString = $"action {_innerAction.Name} was executed as part of {_context.ActionBlockName} action block"; // 执行原始Action逻辑 _innerAction.Execute(); } }
使用时先包装Action,之后直接调用装饰后的实例:
var decoratedAction = new ActionWithContextDecorator(action, context); decoratedAction.Execute();
方案选择建议
- 异步/多线程场景 → 优先选AsyncLocal
- 已使用DI容器 → 优先选依赖注入
- Action与Block固定绑定 → 选上下文绑定属性
- 想保持单一职责、扩展灵活 → 选装饰器模式
内容的提问来源于stack exchange,提问作者user2363676
相关产品推荐
相关产品推荐

