Command Manager
Сегодня я хочу рассказать про шаблон Команда и Менеджер команд. Все к этому плавно и шло. В прошлой статье это так и напрашивалось, но мне было лень это туда вкручивать, решил рассказать отдельно. Хотя сначала у меня были сомнения в том, как это рассказывать и нужно ли вообще. Потому что я с ходу накидал 5 способов реализации (по мере усложнения и развития идеи, но потом один все же забраковал) и этот функционал в еще большей степени развит и встроен в WPF. В итоге я думаю это будет полезно новичкам, чтобы было лучше понятно как это использовать в дальнейшем, в WPF, да и вообще.
Начнем с официальной части, вот что говорит нам википедия по поводу назначения этого шаблона:
Команда — шаблон проектирования, используемый при объектно-ориентированном программировании, представляющий действие. Объект команды заключает в себе само действие и его параметры. Обеспечивает обработку команды в виде объекта, что позволяет сохранять её, передавать в качестве параметра методам, а также возвращать её в виде результата, как и любой другой объект.
Из определения можем сказать, что с помощью команд можно сделать приложение менее связанным друг с другом. Это может быть полезно при написании плагинов, когда есть различные реализации какого-то одного действия, когда надо запускать программу с произвольными начальными действиями определенными в командной строке к примеру.
Большая часть, если не всё, взаимодествие с польщователем в Visual Studio построено на командах. В качестве доказательства можете открыть Tools > Options > Environment > Keyboard и вашему взору предстанут команды. На каждую команду можно назначить горячую клавишу. Это же действие можно совершить из меню. Какие-то действия совершаются из плагинов к студии. Что-то делает студия сама, как результат запоминания ваших действий (открытие последних рабочих файлов при открытии проекта) – все это результат работы шаблона. Иначе была бы просто чудовищная связанность и Overobject – Oriented Programming.
В минусы к этому методу можно записать неочевидность использования в итоговом коде и сложность исследования кода без исполнения его.
Общий вид шаблона представлен на диаграмме ниже.
Не пугаемся, в реальной жизни все как-то попроще будет.
Начало
Начнем с самого простого и элементарного способа.
Команда будет без параметров, просто будет содержимым для какого-либо действия.
public class Command {
private readonly Action action;
public Command(Action action) {
this.action = action;
}
public void Execute() {
if (action == null) return;
action();
}
}
В этом примере действие задается через конструктор, однако вы можете сделать то же самое через свойство, никаких проблем в этом я пока не вижу.
В виду того, что я преподчитаю интерфейсный полиморфизм, для широты использования я выделил интерфейс из этого класса, куда поместил только основную команду на выполнение.
public interface ICommand {
void Execute();
}
На этом костяке и будет строится все остальное, эволюция в действии. Для того, чтобы иметь доступ к предварительно созданным коммандам, необходимо централизованное хранилище. Такое чтобы можно было дотянутся отовсю до него и вызвать необходимую команду в минимум действий. Такое хранилище назовем менеджером команд.
public static class CommandManager {
private static readonly Dictionary<string, ICommand> commands = new Dictionary<string, ICommand>();
public static void RegisterCommand(string commandName, ICommand command) {
commands[commandName] = command;
}
public static void Execute(string commandName) {
commands[commandName].Execute();
}
}
В простейшем виде, класс тоже простой. В данном случае класс сделан статическим, так как в примере никоим образом не присутствует сервис локатор. Хех, хотя сам по себе он тоже менеджер и будет иметь такую же структуру, только отдавать сервисы по интерфейсам.
Итак, в менеджере команд для хранения используется словарь, ключем будет имя команды. Сами команды записываются по интерфейсу. Для пущего удобства я советую определить статический класс с именами команд. Или же можно использовать для этого энумератор.
Использование же будет в духе
var command = new Command(DoJob);
CommandManager.RegisterCommand("Job", command);
И в нужном месте просто вызвать:
CommandManager.Execute("Job");
Улучшение уно
Второй способ расширяет возможности по узнованию и использованию команд. Для этого понадобится только немного улучшить Command Manager. Теперь уникальным ключем будет выступать не только имя команды но и ассоциированный с ней тип. Это позволит создавать одноименные команды для разных типов данных. Достигаться это будет с помощью типа кортеж.
public static class CommandManager {
private static readonly Dictionary<Tuple<string ,Type>, ICommand> commands = new Dictionary<Tuple<string ,Type>, ICommand>();
public static void RegisterCommand(string commandName, ICommand command, Type type) {
commands[new Tuple<string, Type>(commandName,type)] = command;
}
public static void Execute(string commandName, Type type) {
commands[new Tuple<string, Type>(commandName,type)].Execute();
}
}
В остальном все то же самое.
Улучшение дуо
В качестве дальнейшей эволюции команд, было бы неплохо еще задавать условие исполнения команды. Для этого необходимо расширить класс Command. Условие будем задавать как функцию. Т.е. в класс Command дописываем свойство
public Func<bool> CanExecute { get; set; }
Теперь можно изменить метод Execute и внести туда использование нового свойства.
public void Execute() {
if(CanExecute() && action != null)
action();
}
Улучшение трэо
Логическим развитием всего этого будет возможность передачи параметров в команду.
public class Command<T> : ICommand {
public Action<T> Action { private get; set; }
public Func<bool> CanExecute { private get; set; }
private T parameter;
public void SetParameter(object parameter) {
this.parameter = (T) parameter;
}
public Command() {
CanExecute = () => { return true; };
}
public void Execute() {
if (CanExecute() && Action != null)
Action(parameter);
}
}
Команда теперь представляет собой дженерик класс, чтобы можно было применять к любому типу параметров.
CommandManager меняется соответсвенно. Теперь перед в вызов команды будет опционально передаваться параметр.
public static void Execute(string commandName, Type type,object parameters = null) {
var command = commands[new Tuple<string, Type>(commandName, type)];
command.SetParameter(parameters);
command.Execute();
}
В данном случае я применил новую возможность из четвертого фреймворка, автоинициализация параметра. При таком объявлении параметр может быть опциональным, но параметры такого типа должны идти всегда после набора обязательных параметров.
И как тестовый пример (пусть и вырожденный в данном случае)
internal class MyClass {
public MyClass() {
var command = new Command<string> { Action = SomeAction };
CommandManager.RegisterCommand(CommandNames.Open, command, GetType());
CommandManager.Execute(CommandNames.Open, GetType());
CommandManager.Execute(CommandNames.Open, GetType(), @"c:\temp\default.txt");
}
public void SomeAction(string s) {
Console.Out.WriteLine("MC: " + s);
}
}
Если это применять в другом классе, то будет в духе
CommandManager.Execute(CommandNames.Open, typeof(MyClass), "from main thread");
Такой вот менеджер команд должен был бы быть по идее в прошлой статье про межпроцессное взаимодействие.
Исходный код с примерами
Hard’n’heavy.
Нет обратных ссылок на эту запись.

Сентябрь 30th, 2010 - 11:53
hi!This was a really terrific theme!
I come from roma, I was fortunate to come cross your theme in wordpress
Also I get much in your Topics really thank your very much i will come again