如何将参数传递给接口已知的命令?

时间:2013-11-18 12:23:38

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

以下是我想以干净的方式实现的状态: The wish state

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

现在是我到目前为止使用的脏解决方案: The current state

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

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

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

问候,Yggdrasil

4 个答案:

答案 0 :(得分:1)

使用某种工厂来创建/访问ConcreteCommands?

这样Invoker只知道Command接口,而Factory知道如何将它与具体实现配对。

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(); 
}

答案 1 :(得分:1)

如果您的Invoker需要向命令发送参数,并且您确实希望以类型安全的方式执行它(如果我们处理组合而不是继承而通常不那么容易),那么我将参数化Invoker它可以调用的参数类型 - 参数,以便现在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);
    }
}

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

答案 2 :(得分:0)

我在这里可以看到2个选项。首先,您可以在 ConcreteCommand 构造函数中传递参数并将其保留为属性。稍后执行()方法将使用该属性。 如果你想直接从 Invoker 传递一个参数,那么另一个选项。您可以将 ICommand 界面更改为此执行(IArgument arg)每个参数都将从 IArgument 继承,最近将转换为原始类型ConcreteCommand实现。 如果当前的ConcreteCommand实现无法将参数转换为原始类型,比如 ArgumentImpl ,它将跳过执行并屈服于其他 ConcreteCommand 实现

答案 3 :(得分:0)

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

public class Factory {

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

}