7

是否可以使用 Queue of Action 委托来实现 GOF 命令模式?

一段时间以来,我一直试图绕开它,但我很困惑,因为我想添加到队列中的每个可能的操作都可能有不同数量的参数。

有什么建议么?我是否通过专注于命令模式来找出错误的树?

更新:

非常感谢 jgauffin,它很有效......我的实现现在看起来像

public class CommandDispatcher
{
    private readonly Dictionary<Type, List<Action<ICommand>>> _registeredCommands =
        new Dictionary<Type, List<Action<ICommand>>>();

    public void RegisterCommand<T>(Action<ICommand> action) where T : ICommand
    {
        if (_registeredCommands.ContainsKey(typeof (T)))
            _registeredCommands[typeof (T)].Add(action);
        else
            _registeredCommands.Add(typeof (T), new List<Action<ICommand>> {action});
    }

    public void Trigger<T>(T command) where T : ICommand
    {
        if (!_registeredCommands.ContainsKey(typeof(T)))
            throw new InvalidOperationException("There are no subscribers for that command");

        foreach (var registeredCommand in _registeredCommands[typeof(T)])
        {
            registeredCommand(command);
            if (command.Cancel) break;
        }
    }
}
4

3 回答 3

10

你可以使用一个动作。您不应使用多个参数。如果命令需要一个新参数会发生什么?然后,您需要更改调用命令和处理程序的所有位置。

相反,您应该使用具有所有参数作为属性的命令类。通过这种方式,您可以添加参数而不影响代码(新参数应在处理程序中视为可选)。

我会这样做:

public interface ICommand
{
    // Cancel processing, do not invoke any more handlers
    public bool Cancel { get; set; }
}

public class CommandDispatcher 
{
  private Dictionary<Type, List<Action<ICommand>>> _commands = new Dictionary<Type, List<Action<ICommand>>>();


  // Add to dictionary here
  public void Subscribe<T>(Action<T> action) where T : ICommand
  {
      List<Action<ICommand>> subscribers;
      if (!_commands.TryGetValue(typeof(T), out subscribers))
      {
          subscribers = new List<Action<ICommand>>();
          _commands.Add(typeof(T), subscribers));
      }

      subscribers.Add(action);
  }

  // find command and to foreach to execute the actions      
  public void Trigger<T>(T command) where T : ICommand
  {
      List<Action<ICommand>> subscribers;
      if (!_commands.TryGetValue(typeof(T), out subscribers))
          throw new InvalidOperationException("There are no subscribers for that command");

      foreach(var subsriber in subscribers)
      {
          subscriber(command);
          if (command.Cancel)
              break; //a handler canceled the command to prevent others from processing it.
      }
  }

}

public class AddTextCommand : ICommand
{
    public string TextToAdd {get;set;}
}

public class TextHandler
{
    public TextHandler(CommandDispatcher dispatcher)
    {
        disptacher.Subscribe<AddTextCommand>(OnAddText);
    }

    public void OnAddText(AddTextCommand cmd)
    {
        //....
    }
}


public partial class MyForm : Form
{
    CommandDispatcher _dispatcher;

    private void MyTextBox_Changed(object source, EventArgs e)
    {
        _dispatcher.Trigger(new AddTextCommand{TextToAdd = MyTextBox.Text}=;
    } 
}

请注意,该代码是一种伪代码。我直接在答案中写了它而没有测试它。你可能必须改变一些东西才能让它工作,但它至少应该给你一个提示。该实现让您为每个命令添加多个订阅者。

于 2010-10-11T06:15:32.080 回答
3

在命令模式中,典型的命令接口将具有简单的执行方法——这可以用动作委托来表示。但实际实现将由不同的具体类提供,您将/可以在其中传递参数(例如,通过构造函数)。例如:

public interface ICommand 
{
   public void Execute();
}

public class Command1 : ICommand
{
   public Command1(int param1, string param2)
   {
   }

   ...
}

public class Command2 : ICommand
{
  ...
}

public class Program
{

   public static void Main()
   {

       ...

       var commands = new List<Action>();
       commands.Add((new Command1(3, "Hello")).Execute);
       commands.Add((new Command2(...)).Execute);

       ...
   }


}

这里的重点是命令相关的状态和实现将被封装在不同的实现中,而动作委托将指向其实例方法。所以调用委托将导致命令的执行。

于 2010-10-11T06:23:30.047 回答
1

如果您关心参数的数量,那么使用类正确实现命令模式将是正确的方法。Action代表仅限于一名。此外,如果您使用Action委托,您可能希望稍后实现撤消,因为您只是使用委托而不是类,因此您将无法执行该操作。

于 2010-10-11T05:56:34.043 回答