【问题标题】:How can I pass arguments to commands known by interface?如何将参数传递给接口已知的命令?
【发布时间】:2013-12-01 14:24:37
【问题描述】:

以下是我想以干净的方式达到的状态:

如您所见,我的问题是Invoker 只知道Command 接口,但想调用ConcreteCommand。此实现再次需要来自Invoker 的一些参数。

这是我迄今为止使用的肮脏解决方案:

这个解决方案有几个问题:

  • Invoker 拥有关于命令参数的计数和类型的元知识。
  • Invoker 使用魔术字符串来获取所需的命令。
  • Invoker 没有传递正确的参数时,我得到一个运行时错误。我宁愿编译错误。

在不破坏松散耦合的情况下,将参数传递给具体命令的常用方法是什么(例如,Invoker 需要知道 ConcreteCommand)?

问候,世界树

【问题讨论】:

    标签: design-patterns command-pattern loose-coupling


    【解决方案1】:

    如果您的 Invoker 需要向 command 发送参数并且您真的想以类型安全的方式执行此操作(如果我们处理的是对象组合而不是继承,这通常不是那么容易),那么我将参数化 Invoker它可以调用的 arguments-parameters 类型,因此现在 Invoker 可以调用支持此类参数类型的命令。就这样。

    public abstract class Command<T> where T:class
    {
        public abstract void Execute(T par);
    }
    
    public class ConcreteCommand<T> : Command<T> where T : class
    {
        private readonly Receiver<T> _receiver;
    
        public ConcreteCommand(Receiver<T> receiver)
        {
            _receiver = receiver;
        }
    
        #region Overrides of Command<T>
        public override void Execute(T par)
        {
            _receiver.MenuItemClick(par);
        }
        #endregion
    }
    
    public class Invoker<T> where T : class
    {
        private readonly T par;
        private readonly Command<T> cmd;
    
        public Invoker(T par, Command<T> cmd)
        {
            this.par = par;
            this.cmd = cmd;
        }
    
        public void Invoke()
        {
            cmd.Execute(par);
        }
    }
    
    public class Receiver<T> where T : class 
    {
        public void MenuItemClick(T e)
        {
            Console.WriteLine("Parameter types {0}", e.GetType().FullName);
        }
    }
    
    static internal class CmdBuilder
    {
        public static Command<T> PrepareCommand<T>() where T : class
        {
            Receiver<T> rcv = new Receiver<T>();
            Command<T> cmd = new ConcreteCommand<T>(rcv);
            return cmd;
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            var cmd = CmdBuilder.PrepareCommand<EventArgs>();
            cmd.Execute(new EventArgs());
    
            Console.ReadKey(true);
        }
    }
    

    其他选项是使用参数对象。由于所有命令都“知道”它们期望的参数类型,因此它们可以将其转换为参数对象的特定实现。但这不会给你编译错误

    【讨论】:

    • 这很有趣。如果我理解正确,每个命令都会有自己的公共Argument 类。我什至可以使用特定的Argument 类型而不是魔术字符串来请求命令。这可能是一个解决方案。
    • 问题是,将参数传递给执行方法是否是一种好方法。纯命令模式说,execute 方法没有参数,它们在构造函数中传递。如果我需要一个快速且通常称为canExecute 的方法,这又可能会出现问题。总是创建一个新对象是不利的。
    • @Yggdrasil 更新了答案,展示了如何制作命令生成器。 GoF 也有很多例子,根据具体实现和目标模式改变,它们不是教条。至于速度 - 模式通常不会提供额外的速度。灵活性是的,但没有速度。
    【解决方案2】:

    您可以在创建命令实例时传递参数,而不是向“执行”方法发送参数。

    public class Factory {
    
      Command createCommand(Object[] objs,String key){
         return new ConcreteCommand(objs,key);
      }
    
    }
    

    【讨论】:

    • 是的,我知道。但是我有魔术字符串和运行时错误而不是编译错误。
    【解决方案3】:

    我可以在这里看到 2 个选项。首先,您可以在 ConcreteCommand 构造函数中传递一个参数并将其保留为属性。稍后 Execute() 方法将使用该属性。 如果您想直接从 Invoker 传递参数,则另一个选项。您可以将 ICommand 接口更改为此 Execute(IArgument arg) 每个参数将从 IArgument 继承,并且最近将转换为原始类型具体命令实现。 如果当前的 ConcreteCommand 实现无法将参数转换为原始类型,例如 ArgumentImpl,它将跳过 Execute 并让位于其他 ConcreteCommand 实现

    【讨论】:

    • 你的意思是InvokerCommand都需要知道ArgumentImpl吗?
    【解决方案4】:

    使用某种工厂来创建/访问具体命令?

    这样调用者只知道命令接口,工厂知道如何将它与具体实现配对。

    public class Factory {
      // Find by key, can vary implementation at runtime
      Command makeCommand(final String key);
    
      // Compile-safe invocation, but you can still vary implementation at runtime
      Command makeMySpecificCommand(); 
    }
    

    【讨论】:

    • 这类似于我的实现。我可以用这个解决的唯一问题是魔术字符串。论据的问题没有解决。
    • 如果参数从命令更改为命令,那么答案可能是使用 Double Dispatch aka Visitor 模式。您必须在 Invoker 点提供您的意图的一些映射,因此需要进行一些翻译。访问者负责不同的参数签名...
    • 我不知道我是否理解正确。您能否在这里提供一个如何使用访问者模式的简单示例?
    • stackoverflow.com/questions/255214/… 很好地概述了访问者模式的使用。
    • 事情是这样的:你的调用者需要知道它想要命令做什么,所以 /intention/ 在那里确定。您希望通过不将所有具体命令暴露给调用者来将意图与执行它的实际细节分开。这意味着您需要一些其他机制来将意图转换为具体命令。魔术字符串是轻量级的,但容易出错(错别字会导致运行时失败)。第二种选择是工厂或门面。第三个选项是访客。第二个选项可能是您想要的。
    猜你喜欢
    • 1970-01-01
    • 2010-09-11
    • 2021-05-20
    • 1970-01-01
    • 2013-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-01
    相关资源
    最近更新 更多