Шаблон команды: как передать параметры команде?

Мой вопрос связан с шаблоном команды, где у нас есть следующая абстракция (код C#):

public interface ICommand
{
    void Execute();
}

Возьмем простую конкретную команду, цель которой удалить объект из нашего приложения. Например, Person.

У меня будет DeletePersonCommand, который реализует ICommand. Эта команда требует, чтобы Person был удален в качестве параметра, чтобы удалить его при вызове метода Execute.

Как лучше всего управлять параметризованными командами? Как передать параметры командам перед их выполнением?

Я знаю, что этот вопрос возник более четырех лет назад, но Хуанма и bloparod на самом деле дают правильный ответ: сделайте ICommand общим (ICommand<TArgs>). Данный TArgs инкапсулирует все аргументы (он становится Объект параметра). Вам нужно будет создать два объекта для каждой команды: один для сообщения; один для поведения. Поначалу это звучит неловко, но когда вы это поймете, вы никогда не оглянетесь назад. Эта статья подробно описывает эту модель. Обязательно к прочтению всем, кто читает этот вопрос.

Steven 29.12.2012 03:43

@Steven, спасибо за ссылку на ваш пост в блоге. Возможно, было бы хорошо, если бы вы могли пояснить, как подход, который вы описываете в нем, соответствует поставленному здесь вопросу, учитывая, что, по вашему собственному признанию, вы «не рассматриваете [это] как образец команды». Можно подумать, что ваш комментарий - это просто самореклама.

ardila 25.04.2016 13:49
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
63
2
47 132
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

В конструкторе и хранится в виде полей.

Вы также захотите в конечном итоге сделать ваши ICommands сериализуемыми для стека отмены или сохранения файла.

DeletePersonCommand может иметь параметр в своем конструкторе или методах. DeletePersonCommand будет иметь Execute (), и в процессе выполнения можно проверить атрибут, который будет передан Getter / Setter перед вызовом Execute ().

Я бы добавил все необходимые аргументы в конструктор DeletePersonCommand. Затем, когда вызывается Execute(), используются параметры, переданные объекту во время создания.

Пусть «Человек» реализует какой-то интерфейс IDeletable, а затем заставит команду использовать любой базовый класс или интерфейс, который используют ваши сущности. Таким образом, вы можете создать команду DeleteCommand, которая пытается преобразовать сущность в IDeletable, и если это сработает, вызовите .Delete

public class DeleteCommand : ICommand
{
   public void Execute(Entity entity)
   {
      IDeletable del = entity as IDeletable;
      if (del != null) del.Delete();
   }
}

Я не думаю, что это работает - весь смысл ICommand в том, что каждый подкласс переопределяет Execute () точно. Это решение требует, чтобы вызывающий Execute () знал более подробную информацию о типе вызываемой команды.

Matt Dillard 19.09.2008 23:51

Я согласен с Мэттом. В любом случае этот класс DeleteCommand даже не компилируется, поскольку он не реализует void Execute (), как того требует интерфейс ICommand.

chadmyers 19.09.2008 23:52

Используя внедрение зависимостей, вам все равно нужно знать все о типе команды, потому что вам нужно ее обновить! по крайней мере, так ваш код может быть универсальным, если вы имеете дело только с «Сущностью». Исходный ответ включает информацию об изменении ICommand для включения базового класса / интерфейса.

Joel Martinez 19.09.2008 23:57
Ответ принят как подходящий

Вам нужно будет связать параметры с командным объектом либо с помощью конструктора, либо с помощью внедрения установщика (или эквивалентного). Возможно что-то вроде этого:

public class DeletePersonCommand: ICommand
{
     private Person personToDelete;
     public DeletePersonCommand(Person personToDelete)
     {
         this.personToDelete = personToDelete;
     }

     public void Execute()
     {
        doSomethingWith(personToDelete);
     }
}

Именно то, что я бы сделал. Для всего, что неизвестно при создании команды, я бы передал интерфейс службе, которая получает объект при выполнении команды. Это может быть делегат, лямбда-выражение или другой объект.

Hamish Smith 20.09.2008 04:11

Это плохое решение, потому что контейнер тесно связан с Person, вместо этого эта связь должна быть нарушена с помощью какого-то объекта Parameter, который содержит зависимости. «Скажи, не спрашивай» - вот главное правило.

Martin of Hessle 19.03.2019 14:13

@Blair Conrad Интересно, а что, если мы изменим метод приемника. В соответствии с принципом открытия / закрытия, изменение метода выполнения команды будет в порядке?

uzay95 13.11.2019 04:09

Есть несколько вариантов:

Вы можете передавать параметры по свойствам или конструктору.

Другой вариант:

interface ICommand<T>
{
    void Execute(T args);
}

И инкапсулируйте все параметры команды в объект значения.

Проблема с приведенным выше кодом заключается в том, что разные команды (например, CreateSomeThingCommand и DeleteSomethingCommand) могут требовать разных параметров и больше не могут выполняться одинаково (учитывая вызов IEnumerable <ICommand> .Execute ()). Шаблон команды предназначен для использования для отделения определения от выполнения ... если вы передаете параметры во время выполнения, вы изменяете / контролируете поведение команды во время выполнения, а не во время определения.

Beachwalker 12.09.2012 15:42

Кстати: я думаю, вы имели в виду void Execute (T args) вместо Execute <T> (T args>, потому что T уже определен в ICommand <T>, второй на уровне функции / метода бесполезен. Вы можете создать тоже что-то вроде следующего: interface ICommand <T1> {void Execute <T2> (T1 t1, T2 t2);} (что делает больше смысла) или interface ICommand <T1> {void Execute <T2> (T2 t2); // использование T1 в другом месте}

Beachwalker 12.09.2012 16:23

Передайте человека при создании объекта команды:

ICommand command = new DeletePersonCommand(person);

так что когда вы выполняете команду, он уже знает все, что ему нужно знать.

class DeletePersonCommand : ICommand
{
   private Person person;
   public DeletePersonCommand(Person person)
   {
      this.person = person;
   }

   public void Execute()
   {
      RealDelete(person);
   }
}

В этом случае то, что мы сделали с нашими объектами Command, - это создать объект Context, который по сути является картой. Карта содержит пары «имя-значение», где ключи являются константами, а значения - параметрами, которые используются реализациями Command. Это особенно полезно, если у вас есть цепочка команд, в которой последующие команды зависят от изменений контекста более ранних команд.

Таким образом, фактический метод становится

void execute(Context ctx);

Я использовал это в своем дизайне, но «Контекст» был Dictionary <string, object>.

Jutanium 02.01.2013 05:17

На основе шаблона в C# / WPF интерфейс ICommand (System.Windows.Input.ICommand) определен для приема объекта в качестве параметра в Execute, а также метода CanExecute.

interface ICommand
            {
                bool CanExecute(object parameter);
                void Execute(object parameter);
            }

Это позволяет вам определить свою команду как статическое общедоступное поле, которое является экземпляром вашего настраиваемого объекта команды, реализующего ICommand.

public static ICommand DeleteCommand = new DeleteCommandInstance();

Таким образом, соответствующий объект, в вашем случае человек, передается при вызове execute. Затем метод Execute может привести объект и вызвать метод Delete ().

public void Execute(object parameter)
            {
                person target = (person)parameter;
                target.Delete();
            } 

То, как «шаблон» реализован таким образом, представляет собой не что иное, как «специальный» делегат с проверкой (CanExecute). Я думаю, это теряет реальную функциональность, для которой создан шаблон ... разделения определения и выполнения команды. Передача параметров изменит / может изменить способ выполнения. Таким образом, определение команды берется из конструктора команды во время создания параметра. (Я знаю, что M $ использовал это для целей графического интерфейса, но я не думаю, что это должен быть общий подход для реализации шаблона команд.)

Beachwalker 12.09.2012 15:50

Вы должны создать объект CommandArgs, содержащий параметры, которые вы хотите использовать. Вставьте объект CommandArgs с помощью конструктора объекта Command.

Почему бы не ввести необходимые параметры или значение делегатом Func <MyParam>?

Beachwalker 12.09.2012 15:52

Передача данных через конструктор или сеттер работает, но требует, чтобы создатель команды знал, какие данные нужны команде ...

Идея «контекста» действительно хороша, и я работал над (внутренней) структурой, которая использовала ее некоторое время назад.

Если вы настроили свой контроллер (компоненты пользовательского интерфейса, которые взаимодействуют с пользователем, интерфейс командной строки, интерпретирующий пользовательские команды, сервлет, интерпретирующий входящие параметры и данные сеанса и т. д.), Чтобы предоставить именованный доступ к доступным данным, команды могут напрямую запрашивать данные, которые им нужны.

Мне очень нравится разделение, которое позволяет такая установка. Подумайте о наслоении следующим образом:

User Interface (GUI controls, CLI, etc)
    |
[syncs with/gets data]
    V
Controller / Presentation Model
    |                    ^
[executes]               |
    V                    |
Commands --------> [gets data by name]
    |
[updates]
    V
Domain Model

Если вы сделаете это «правильно», те же самые команды и модель представления можно будет использовать с любым типом пользовательского интерфейса.

Если пойти дальше, то «контроллер» в приведенном выше примере довольно общий. Элементы управления пользовательского интерфейса должны знать только имя команды, которую они будут вызывать - им (или контроллеру) не нужно знать, как создать эту команду или какие данные ей нужны. В этом настоящее преимущество.

Например, вы можете сохранить имя команды для выполнения на карте. Каждый раз, когда компонент «запускается» (обычно это actionPerformed), контроллер ищет имя команды, создает ее экземпляр, вызывает execute и помещает ее в стек отмены (если вы его используете).

Моя реализация была бы такой (с использованием ICommand, предложенного Хуанмой):

public class DeletePersonCommand: ICommand<Person>
{
    public DeletePersonCommand(IPersonService personService)
    {  
        this.personService = personService;
    }

    public void Execute(Person person)
    {
        this.personService.DeletePerson(person);
    }
}

IPersonService может быть IPersonRepository, это зависит от того, на каком «уровне» находится ваша команда.

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

Beachwalker 12.09.2012 16:18

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

   class DeletePersonCommand implements ICommand
{
     private Supplier<Person> personSupplier;

     public DeletePersonCommand(Supplier<Person> personSupplier)
     {
         this.personSupplier = personSupplier;
     }

     public void Execute()
     {
        personSupplier.get().delete();
     }
}

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

Полезный связь по поставщику.

ПРИМЕЧАНИЕ: код написан на java. Кто-то со знанием C# может это настроить.

Другие вопросы по теме