命令模式:如何将参数传递给命令?
我的问题与命令模式有关,在命令模式中,我们具有以下抽象(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,它取决于命令的"层"。