如何在不修改命令接口的前提下向装饰器传递上下文数据?
如何在不修改命令接口的前提下向装饰器传递上下文数据?
嗨,这个问题我之前做类似的命令扩展功能时也碰到过,完全理解你不想破坏原有接口设计的心情!下面给你几个实用的方案,都不用动你的IActionCommand接口和现有命令类:
方案一:扩展ActionModel存储上下文(最推荐)
你的ActionModel本来就定义为“可以包含任何与动作相关的参数、元数据”,那直接给它加一个可选的上下文属性就好了,完全符合它的设计初衷,也不用改接口方法签名。
首先扩展你的模型类:
// 新增上下文类,用来存触发源等信息 public class CommandExecutionContext { public string TriggerSource { get; set; } // 比如"Hotkey"、"UIButton" // 还可以加其他需要的上下文字段,比如触发时间、用户ID等 } public class ActionModel { // 你原有的属性 public object ActionParameters { get; set; } // 新增可选的上下文属性,默认可以为null public CommandExecutionContext ExecutionContext { get; set; } }
然后修改你的分析装饰器,读取这个上下文:
public class AnyActionCommandAnalyticsDecorator : ActionCommandDecorator { private readonly AnalyticsService _analyticsService = new AnalyticsService(); public AnyActionCommandAnalyticsDecorator(IActionCommand decorator) : base(decorator) { } public override void ExeciteAction(ActionModel actionModel) { // 读取触发源,默认设为"Unknown"避免空引用 var triggerSource = actionModel.ExecutionContext?.TriggerSource ?? "Unknown"; _analyticsService.SendData(new AnalyticsModel { TriggerSource = triggerSource }); // 执行原命令逻辑 Decorator.ExeciteAction(actionModel); } }
调用的时候,只需要在创建ActionModel时传入上下文就行:
// UI按钮触发的场景 var uiActionModel = new ActionModel { ActionParameters = yourParams, ExecutionContext = new CommandExecutionContext { TriggerSource = "UIButton" } }; var command = new AnyActionCommandAnalyticsDecorator(new AnyActionCommand()); command.ExeciteAction(uiActionModel); // 快捷键触发的场景同理 var hotkeyActionModel = new ActionModel { ActionParameters = yourParams, ExecutionContext = new CommandExecutionContext { TriggerSource = "Hotkey" } }; command.ExeciteAction(hotkeyActionModel);
这个方案最省心,完全贴合现有代码结构,也容易维护和扩展。
方案二:用AsyncLocal存储上下文(适合复杂调用链)
如果你的命令调用链比较长,或者上下文数据不好直接通过ActionModel传递(比如中间层不允许修改ActionModel),可以用.NET的AsyncLocal来存储上下文——它能保证在同一个异步执行流里,上下文数据是隔离的,不会互相干扰。
先创建一个静态的上下文持有者:
public static class CommandContextHolder { // AsyncLocal适合异步场景,同步场景用ThreadLocal也可以 private static readonly AsyncLocal<CommandExecutionContext> _currentContext = new AsyncLocal<CommandExecutionContext>(); public static CommandExecutionContext CurrentContext { get => _currentContext.Value; set => _currentContext.Value = value; } }
然后在触发命令之前设置上下文,执行完记得清理:
// 快捷键触发的场景 CommandContextHolder.CurrentContext = new CommandExecutionContext { TriggerSource = "Hotkey" }; try { var command = new AnyActionCommandAnalyticsDecorator(new AnyActionCommand()); command.ExeciteAction(actionModel); } finally { // 必须清理,避免污染后续的命令执行 CommandContextHolder.CurrentContext = null; }
最后在装饰器里直接读取上下文:
public override void ExeciteAction(ActionModel actionModel) { var triggerSource = CommandContextHolder.CurrentContext?.TriggerSource ?? "Unknown"; _analyticsService.SendData(new AnalyticsModel { TriggerSource = triggerSource }); Decorator.ExeciteAction(actionModel); }
这个方案的好处是完全不用修改任何模型或接口,缺点是上下文是隐式传递的,新人接手可能需要花点时间理解数据流向。
方案三:给装饰器添加可设置的属性(适合上下文固定的场景)
如果某个命令实例的触发源是固定的(比如某个按钮绑定的命令永远是UI触发),可以直接给装饰器加一个属性来存储上下文:
修改你的分析装饰器:
public class AnyActionCommandAnalyticsDecorator : ActionCommandDecorator { private readonly AnalyticsService _analyticsService = new AnalyticsService(); // 新增属性,允许外部设置触发源 public string TriggerSource { get; set; } = "Unknown"; public AnyActionCommandAnalyticsDecorator(IActionCommand decorator) : base(decorator) { } public override void ExeciteAction(ActionModel actionModel) { _analyticsService.SendData(new AnalyticsModel { TriggerSource = TriggerSource }); Decorator.ExeciteAction(actionModel); } }
调用时直接设置属性:
// 绑定到UI按钮的命令,触发源固定为UIButton var uiCommand = new AnyActionCommandAnalyticsDecorator(new AnyActionCommand()) { TriggerSource = "UIButton" }; uiCommand.ExeciteAction(actionModel);
这个方案简单直接,但只适合上下文固定的场景,如果同一个命令实例需要不同的触发源,就不太适用了。
总体来说,优先选方案一,它最符合你的现有设计;如果有特殊场景限制,再考虑方案二或三。
内容来源于stack exchange
相关产品推荐
相关产品推荐

