Реализация шаблона команды с использованием делегата C # Action - PullRequest
7 голосов
/ 11 октября 2010

Возможно ли реализовать шаблон команды GOF с использованием делегатов очереди действий?

Я некоторое время пытался обернуть голову вокруг, и я в замешательстве, потому что каждое из возможных действий, которые я хочу добавить в очередь, может иметь различное количество параметров.

Есть предложения? Я лаю не на том дереве, сосредоточившись на шаблоне команды?

UPDATE:

Большое спасибо 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;
        }
    }
}

Ответы [ 3 ]

10 голосов
/ 11 октября 2010

Вы можете использовать Действие.Вы не должны использовать несколько параметров.Что произойдет, если команде нужен новый параметр?Затем вам нужно будет поменять все места, используя команду плюс обработчик.

Вместо этого вы должны использовать классы Command, у которых все параметры являются свойствами.Таким образом, вы можете добавлять параметры, не влияя на код (новые параметры должны быть обработаны как необязательные в обработчике).

Вот как я бы это сделал:

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}=;
    } 
}

Примечаниечто код является своего рода псевдокодом.Я написал это прямо в ответе, не проверяя это.Вам, вероятно, придется изменить материал, чтобы заставить его работать, но это должно по крайней мере дать вам подсказку.Реализация позволяет добавлять несколько подписчиков для каждой команды.

3 голосов
/ 11 октября 2010

В шаблоне команд типичный командный интерфейс будет иметь простой метод execute - это может быть представлено делегатом Action.Но фактическая реализация будет обеспечиваться различными конкретными классами, где вы будете / можете передавать параметры (например, через конструктор).Например:

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

       ...
   }


}

Дело в том, что связанное с командой состояние и реализация будут инкапсулированы в другой реализации, а делегат Action будет указывать на его метод экземпляра.Таким образом, вызов делегата приведет к выполнению команды.

1 голос
/ 11 октября 2010

Если вас интересует количество параметров, то правильная реализация шаблона команды с использованием класса будет правильным решением.Делегат Action ограничен только одним.Кроме того, если вы используете делегат Action, вы, возможно, захотите реализовать отмену позже, что вы не сможете сделать, поскольку вы просто использовали делегат вместо класса.

...