命令模式:如何将参数传递给命令?

时间:2020-03-06 14:27:32  来源:igfitidea点击:

我的问题与命令模式有关,在命令模式中,我们具有以下抽象(Ccode):

public interface ICommand
{
    void Execute();
}

让我们采用一个简单的具体命令,该命令旨在从我们的应用程序中删除一个实体。例如,一个"人"实例。

我将有一个实现" ICommand"的" DeletePersonCommand"。该命令需要" Person"作为参数删除,以便在调用" Execute"方法时将其删除。

管理参数化命令的最佳方法是什么?在执行参数之前如何将参数传递给命令?

解决方案

在构造函数中并存储为字段。

我们还需要最终使ICommands可序列化,以实现撤消堆栈或者文件持久性。

DeletePersonCommand的构造函数或者方法中可以包含参数。 DeletePersonCommand将具有Execute(),并且在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();
   }
}

我们需要通过构造函数或者setter注入(或者等效方法)将参数与命令对象相关联。也许是这样的:

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

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

有一些选项:

我们可以通过属性或者构造函数传递参数。

其他选项可能是:

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

并将所有命令参数封装在一个值对象中。

创建命令对象时传递人员:

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

基于C#/ WPF中的模式,定义了ICommand接口(System.Windows.Input.ICommand),以将对象以及Execute方法作为Execute上的参数。

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

我们应该创建一个CommandArgs对象以包含要使用的参数。使用Command对象的构造函数注入CommandArgs对象。

通过构造器或者设置器传递数据是可行的,但是要求命令的创建者知道命令所需的数据...

"上下文"的想法确实很好,并且我正在研究(内部)框架,该框架在不久之前就已经利用了它。

如果我们设置控制器(与用户交互的UI组件,CLI解释用户命令,servlet解释传入的参数和会话数据等)以提供对可用数据的命名访问,则命令可以直接要求他们想要的数据。

我真的很喜欢这种设置允许的分离。考虑如下分层:

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

如果我们这样做"正确",则相同的命令和表示模型可以与任何类型的用户界面一起使用。

更进一步,上面的"控制器"非常通用。 UI控件仅需要知道它们将调用的命令的名称-它们(或者控制器)不需要了解如何创建该命令或者该命令需要什么数据。这才是真正的优势。

例如,我们可以保存要在Map中执行的命令的名称。每当"触发"该组件(通常是一个actionPerformed)时,控制器就会查找命令名称,实例化它,调用execute并将其压入撤消堆栈(如果使用一个)。

我的实现是这样的(使用Juanma提出的ICommand):

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

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

IPersonService可以是IPersonRepository,它取决于命令的"层"。