コマンドデザインパターン を勉強していますが、その使用方法にかなり混乱しています。私が持っている例は、ライトをオンまたはオフにするために使用されるリモートコントロールクラスに関連しています。
最終的にswitchOn/switchOffメソッドを呼び出す個別のクラスとメソッドを用意するのではなく、LightクラスのswitchOn()/ switchOff()メソッドを使用しないのはなぜですか?
私の例は非常に単純ですが、それがポイントです。コマンドデザインパターンの正確な使用法を確認するために、インターネット上のどこにも複雑な問題を見つけることができませんでした。
あなたが解決した複雑な現実世界の問題を知っているなら、このデザインパターンを使って解決できることを私と共有してください。私とこの投稿の今後の読者は、このデザインパターンの使用法をよりよく理解するのに役立ちます。ありがとう
//Command
public interface Command {
public void execute();
}
//Concrete Command
public class LightOnCommand implements Command {
//Reference to the light
Light light;
public LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.switchOn(); //Explicit call of selected class's method
}
}
//Concrete Command
public class LightOffCommand implements Command {
//Reference to the light
Light light;
public LightOffCommand(Light light) {
this.light = light;
}
public void execute() {
light.switchOff();
}
}
//Receiver
public class Light {
private boolean on;
public void switchOn() {
on = true;
}
public void switchOff() {
on = false;
}
}
//Invoker
public class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
}
}
//Client
public class Client {
public static void main(String[] args) {
RemoteControl control = new RemoteControl();
Light light = new Light();
Command lightsOn = new LightsOnCommand(light);
Command lightsOff = new LightsOffCommand(light);
//Switch on
control.setCommand(lightsOn);
control.pressButton();
//Switch off
control.setCommand(lightsOff);
control.pressButton();
}
}
次のようなコードを簡単に使用できないのはなぜですか?
Light light = new Light();
switch(light.command) {
case 1:
light.switchOn();
break;
case 2:
light.switchOff();
break;
}
Commandパターンを使用する主な動機は、コマンドのエグゼキューターが、コマンドが何であるか、どのコンテキスト情報が必要か、何をするかについて何も知る必要がないことです。これらはすべてコマンドにカプセル化されています。
これにより、順番に実行されるコマンド、他のアイテムに依存するコマンド、トリガーイベントに割り当てられるコマンドのリストなどを実行できます。
あなたの例では、他のクラスを持つことができます(例えばAir Conditioner
)独自のコマンド(例:Turn Thermostat Up
、Turn Thermostat Down
)。これらのコマンドはいずれもボタンに割り当てられたり、コマンドの知識を必要とせずに何らかの条件が満たされたときにトリガーされたりします。
したがって、要約すると、パターンはアクションを実行するために必要なすべてをカプセル化し、そのコンテキストのいずれからも完全に独立してアクションの実行を可能にします。それがあなたの要件ではない場合、パターンはおそらくあなたの問題のスペースに役に立たないでしょう。
簡単な使用例を次に示します。
interface Command {
void execute();
}
class Light {
public Command turnOn();
public Command turnOff();
}
class AirConditioner {
public Command setThermostat(Temperature temperature);
}
class Button {
public Button(String text, Command onPush);
}
class Scheduler {
public void addScheduledCommand(Time timeToExecute, Command command);
}
その後、次のようなことができます。
new Button("Turn on light", light.turnOn());
scheduler.addScheduledCommand(new Time("15:12:07"), airCon.setThermostat(27));
scheduler.addScheduledCommand(new Time("15:13:02"), light.turnOff());
ご覧の通り、Button
とScheduler
はコマンドについて何も知る必要はありません。 Scheduler
は、コマンドのコレクションを保持する可能性のあるクラスの例です。
また、Java 8つの機能的インターフェースとメソッド参照により、このタイプのコードがよりきれいになりました。
@FunctionalInterface
interface Command {
void execute();
}
public Light {
public void turnOn();
}
new Button("Turn On Light", light::turnOn);
これで、コマンドに変換されるメソッドはコマンドについて知る必要すらありません。正しいシグネチャを持っている限り、メソッドを参照することで匿名コマンドオブジェクトを静かに作成できます。
コマンド設計の非実装の側面と、2つの主要なカテゴリにグループ化されたコマンド設計パターンを使用する主な理由に注目しましょう。
ほとんどのプログラミングでは、実装を隠して、最上位の問題を見たときに、コマンド/コードの包括的なサブセットで構成されるようにします。つまりあなたは、ライトのスイッチが入る方法や車が始動する方法についての厄介な詳細を知る必要はありません。車を始動させることに焦点を合わせている場合、エンジンの仕組み、エンジンに入るための燃料の必要性、バルブの仕組みなどを理解する必要はありません...
コマンドは、このようなビューを提供します。 TurnLightOn
コマンドの機能、またはStartCar
がすぐに理解できます。 コマンドを使用すると、実行されるアクションを明確に示しながら、何かの実行方法の詳細を非表示にします。
さらに、後でLight
クラス全体、またはCar
クラス全体を再構築します。これにより、いくつかの異なるオブジェクトをインスタンス化する必要があります。 。この場合、直接アクセス方式を多くの場所で実装している場合、事前にコーディングしたすべての場所で変更する必要があります。 コマンドを使用すると、コマンドの呼び出しを変更せずに、物事を行う方法の内部詳細を変更できます。
コマンドインターフェイスを使用すると、コマンドを使用するコードと、コマンドの実際のアクションを実行するコードの間に追加のレイヤーが提供されます。これにより、複数の良いシナリオが可能になります。
コマンドインターフェイスを使用して、オブジェクトへのアクセスを制限し、別のレベルのセキュリティを定義することもできます。内部の特殊なケースを簡単に処理できるように、かなりオープンなアクセスを備えたモジュール/ライブラリを用意することは理にかなっています。
ただし、外部からは、ライトへのアクセスを制限して、オンまたはオフのみにすることができます。 コマンドを使用すると、インターフェイスをクラスに制限することができます。
さらに、必要に応じて、コマンドを中心に専用のユーザーアクセスシステムを構築できます。これにより、すべてのビジネスロジックが開かれた状態でアクセス可能になり、制限がなくなりますが、コマンドレベルでアクセスを簡単に制限して適切なアクセスを強制できます。
十分な大規模システムを構築する場合、コマンドを使用すると、さまざまなモジュール/ライブラリ間をブリッジできます。特定のクラスのすべての実装の詳細を調べる必要がある代わりに、どのコマンドがクラスにアクセスしているかを調べることができます。
また、コマンド自体に実装の詳細を残しているため、一般的な方法を使用してコマンドをインスタンス化し、実行し、結果を確認できます。これにより、特定のLight
またはCar
クラスをインスタンス化し、その結果を決定する方法を調べる必要がなく、コーディングが簡単になります。
コマンドを使用して、コマンドの順序付けなどを行うこともできます。 TurnOnLight
コマンドを実行するかStartCar
コマンドを実行するかは実際には関係ないため、これらのシーケンスは同じ方法で実行されるため実行できます。これにより、コマンドチェーンの実行が可能になり、複数の状況で役立つ可能性があります。
マクロを構築し、グループ化されていると思われるコマンドのセットを実行できます。つまりコマンドシーケンス:UnlockDoor
、EnterHouse
、TurnOnLight
。コマンドの自然なシーケンス。ただし、異なるオブジェクトとアクションを使用するため、メソッドにはなりそうにない。
コマンドの性質がかなり小さいため、シリアル化も非常にうまく行えます。これは、サーバーとクライアントのコンテキスト、またはプログラムとマイクロサービスのコンテキストで役立ちます。
サーバー(またはプログラム)はコマンドをトリガーし、コマンドをシリアル化して、通信プロトコル(イベントキュー、メッセージキュー、httpなど)を介して実際にコマンドを処理する人に送信します。最初にサーバーでオブジェクトをインスタンス化する必要はありません。つまり、Light
は軽量(しゃれを意図)にすることも、Car
は本当に大きな構造にすることもできます。必要なのはコマンドと、場合によってはいくつかのパラメーターだけです。
これは、今後の研究のために CQRS-コマンドクエリの責任分離パターン を紹介するのに適した場所です。
システムでコマンドの追加レイヤーを使用すると、ビジネスニーズの場合、コマンドのログ記録または追跡が可能になります。これをあちこちで行う代わりに、コマンドモジュール内で追跡/ログを収集できます。
これにより、ロギングシステムを簡単に変更したり、時間を計ったり、ロギングを有効/無効にしたりできます。そして、それはすべてコマンドモジュール内で簡単に管理できます。
また、コマンドを追跡すると、特定の状態からコマンドを繰り返し実行することを選択できるため、元に戻す操作を許可できます。少し追加のセットアップが必要ですが、簡単に実行できます。
要するに、コマンドはすぐに大規模になるプログラムのさまざまな部分間の接続を可能にし、軽量で簡単に記憶/文書化され、必要に応じて実装の詳細を隠すため、非常に便利です。さらに、コマンドはいくつかの便利な拡張を可能にします。これは、より大きなシステムを構築するときに便利です。つまり、共通のインターフェース、シーケンス、シリアル化、追跡、ロギング、セキュリティです。
可能性は多数ありますが、通常は次のようになります。
opts.register("--on", new LightOnCommand())
などのアクションを登録できます。on(Event.ENTER_ROOM, new LightOnCommand())
などのイベントがトリガーされたときにコールバックを登録するここでの一般的なパターンは、someアクションはそのアクションが何であるかを知らずに実行する必要があることを理解するコードの一部を持ち、コードの別の部分はその方法を知っていることですアクションですが、いつ実行するかではありません。
たとえば、その最初の例では、opts
インスタンスは、コマンドラインオプション--onが表示されたときにライトをオンにする必要があることを認識しています。しかし、「ライトをオンにする」ことの意味を実際に知ることなく、これを知っています。実際、optsインスタンスはサードパーティのライブラリからのものである可能性が高いため、できませんライトについて知っています。知っているのは、解析するコマンドラインオプションにアクション(コマンド)を関連付ける方法だけです。
する必要はありません。設計パターンは、単純にガイドラインであり、過去の一部の人々は、非常に複雑なアプリケーションを作成するときに便利だと感じています。
あなたの場合、あなたがしなければならないのがライトスイッチのオンとオフを切り替えることであり、それ以外のことはあまりない場合、2番目の選択肢は簡単です。
ほとんどの場合、コードが少ないほど多くのコードよりも優れています。
指定するICommand
の例はかなり制限されており、ラムダ式を持たないプログラミング言語でのみ実際に使用されます。 Sprinterは、コマンドファクトリーの使用を示す彼の回答でこれをうまくカバーしています。
コマンドパターンのほとんどの場合には、CanRun
やUndo
などの他のメソッドが含まれます。これらにより、ボタンはコマンドの状態に基づいて有効状態を更新したり、アプリケーションが取り消しスタックを実装したりできます。
ほとんどのデザインパターンと同様に、コマンドパターンは、物事が少し複雑になる独自の機能を備えています。また、よく知られているため、ほとんどのプログラマーにコードを明確に伝えるのに役立ちます。
私が経験したコマンドパターンにはいくつかの利点があります。しかし、まず第一に、このパターンは他のすべてのパターンと同様に、コードの可読性を高め、(おそらく)共有コードベースに共通の理解をもたらすことを思い出したいと思います。
このパターンを使用して、メソッド指向のインターフェイスからコマンド指向のインターフェイスに移行します。つまり、メソッド呼び出しを必要なデータとともに具体的なコマンドにカプセル化しています。はい、コードをより読みやすくしますが、より重要なことは、メソッドをオブジェクトとして扱うことができ、コードの複雑さを増すことなく、コマンドを簡単に追加/削除/変更できることです。そのため、管理も読みやすくなります。
第二に、メソッドをオブジェクトとして持っているので、後で実行するためにそれらを保存/キューに入れることができます。または、それらを実行した後でもキャンセルします。これは、このパターンが「元に戻す」の実装に役立つ場所です。
最後に、コマンドパターンは、コマンドの実行とコマンド自体を分離するためにも使用されます。ウェイターが受け取った注文を調理する方法を知らないようです。彼は気にしません。彼は知る必要はありません。彼がそれを知っていたら、彼は料理人としても働いていたでしょう。そして、レストランのオーナーがウェイターを解雇したい場合、彼/彼女は料理人もいないことになります。もちろん、この定義は特別なものではなく、コマンドパターンのみに関連するものでもありません。これが、一般的にデカップリングまたは依存関係管理が必要な理由の説明です。
考えられることは何でもできますが、コードのusability
のmanageability
のパターンに従うのが良いでしょう。
実際の例では、Light
がインターフェースになります。 Light
、LEDLight
のようなTubeLight
の異なる実装があります
Inovker
(RemoteControl
)を介して具体的なコマンドを実行する場合、Receiver
のメソッド名の変更について心配する必要はありません。 switchOn() in Light can be changed to switchOnDevice() in future
。ただし、ConcreteCommand
(LightsOnCommand
)が関連する変更を行うため、Invokerには影響しません。
1つのサービス(サービスA)にインターフェースを公開し、他のサービス(サービスB)に実装するシナリオを想定します。
これで、サービスAはReceiver
の変更について知らないはずです。
Invoker
は、メッセージの送信者と受信者の間の疎結合を提供します。
さらに関連するSEの質問をご覧ください。
設計パターンは、基本的に複雑な問題を解決するために開発されたものです。つまり、ソリューションが複雑になるのを防ぐために使用されていると言えます。既存のコードに大きな変更を加えることなく、将来的に新しい機能を追加する柔軟性を提供します。 拡張用にオープン、変更用にクローズ
コマンドパターンは、基本的にリクエストをオブジェクトとしてカプセル化するため、異なるリクエストで他のオブジェクトをパラメーター化して、取り消し可能な操作をサポートできます。
コマンドパターンの最良の例であるリモートコントロールを見たとき。この場合、各ボタンに具体的なコマンドを関連付けており、そのコマンドには受信者の情報が含まれています。
スイッチケースの例では、リモートコントロールのボタンをライトではなくファンに関連付ける場合、ボタンをコマンドに関連付ける必要があるため、既存のコードを再度変更する必要があるとします。これはコマンドパターンの本質であり、コマンドをボタンに柔軟に関連付けて、実行時に機能を変更できるようにします。一般的なテレビのリモコンでは、この実現可能性はありませんが、ボリュームを下げるボタンとしてボリュームを上げることはできません。ただし、アプリケーションを使用してテレビを制御する場合は、任意のボタンを使用可能なコマンドに割り当てることができます。
さらに、コマンドパターンを使用すると、特定の機能を実現するための一連のコマンド、つまりマクロコマンドを使用できます。したがって、より広いレベルで考える場合、このパターンにより、機能を拡張する柔軟性が得られます。
コマンドパターンは、コマンドオブジェクト(LightOnCommand、FanOffCommandなど)を使用して、invoker(リモートコントロール)とReceiver(Light、Fanなど)を分離するのに役立ちます。