私の質問は、次の抽象化(C#コード)があるコマンドパターンに関連しています。
public interface ICommand
{
void Execute();
}
アプリケーションからエンティティを削除することを目的とした簡単な具体的なコマンドを見てみましょう。たとえば、Person
インスタンス。
DeletePersonCommand
を実装するICommand
を用意します。このコマンドには、Person
メソッドが呼び出されたときに削除するために、パラメーターとして削除するExecute
が必要です。
パラメータ化されたコマンドを管理する最良の方法は何ですか?コマンドを実行する前にコマンドにパラメーターを渡す方法は?
コンストラクターまたはセッターインジェクション(または同等の方法)によって、パラメーターをコマンドオブジェクトに関連付ける必要があります。おそらくこのようなもの:
public class DeletePersonCommand: ICommand
{
private Person personToDelete;
public DeletePersonCommand(Person personToDelete)
{
this.personToDelete = personToDelete;
}
public void Execute()
{
doSomethingWith(personToDelete);
}
}
コンストラクターまたはセッターを介してデータを渡すことはできますが、コマンドの作成者はコマンドが必要とするデータを知っている必要があります...
「コンテキスト」のアイデアは本当に良いものであり、私は以前にそれを活用する(内部)フレームワークに取り組んでいました。
コントローラー(ユーザーと対話するUIコンポーネント、ユーザーコマンドを解釈するCLI、受信パラメーターとセッションデータを解釈するサーブレットなど)を設定して、使用可能なデータへの名前付きアクセスを提供する場合、コマンドは必要なデータを直接要求できます。
私はこのようなセットアップが可能にする分離が本当に好きです。次のように階層化について考えます。
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コントロールは、呼び出すコマンドのnameを知るだけで済みます-それら(またはコントローラー)は、そのコマンドの作成方法やコマンドのデータを知る必要はありません。ニーズ。ここが本当の利点です。
たとえば、実行するコマンドの名前をマップで保持できます。コンポーネントが「トリガー」されると(通常actionPerformed)、コントローラーはコマンド名を検索し、インスタンス化し、executeを呼び出し、アンドゥスタックにプッシュします(使用する場合)。
いくつかのオプションがあります:
プロパティまたはコンストラクターでパラメーターを渡すことができます。
その他のオプションは次のとおりです。
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);
}
}
私の実装はこれです(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である可能性がありますが、それはコマンドの「レイヤー」に依存します。
この場合、Commandオブジェクトで行ったのは、本質的にマップであるContextオブジェクトを作成することです。マップには名前と値のペアが含まれており、キーは定数であり、値はコマンドの実装で使用されるパラメーターです。後のコマンドが前のコマンドからのコンテキストの変更に依存する一連のコマンドがある場合に特に便利です。
実際の方法は
void execute(Context ctx);
コンストラクターで、フィールドとして保存されます。
また、最終的には、Undoスタックまたはファイルの永続化のためにICommandをシリアル化できるようにする必要があります。
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();
}
使用するパラメーターを含むCommandArgsオブジェクトを作成する必要があります。 Commandオブジェクトのコンストラクターを使用してCommandArgsオブジェクトを注入します。
DeletePersonCommandは、コンストラクターまたはメソッドにパラメーターを持つことができます。 DeletePersonCommandにはExecute()があり、executeでは、Getter/Setterが以前にExecute()の呼び出しに渡す属性をチェックできます。
必要な引数をDeletePersonCommand
のコンストラクターに追加します。次に、Execute()
が呼び出されると、構築時にオブジェクトに渡されたパラメーターが使用されます。