Javaのヒント68:Javaでコマンドパターンを実装する方法を学ぶ
デザインパターンは、オブジェクト指向(OO)プロジェクトの設計フェーズを加速するだけでなく、開発チームの生産性とソフトウェアの品質を向上させます。Commandパターンは、私たちは、送信者と受信者の間で完全デカップリングを達成することを可能にするオブジェクトの行動パターンです。(送信者は操作を呼び出すオブジェクトであり、受信者は特定の操作を実行する要求を受信するオブジェクトです。デカップリングを使用すると、送信者はReceiver
のインターフェイスを認識しません。)要求という用語ここでは、実行されるコマンドを指します。コマンドパターンを使用すると、要求がいつどのように実行されるかを変更することもできます。したがって、コマンドパターンは、柔軟性と拡張性を提供します。
Cのようなプログラミング言語では、関数ポインタを使用して巨大なswitchステートメントを排除します。(詳細については、「Javaのヒント30:ポリモーフィズムとJava」を参照してください。)Javaには関数ポインターがないため、コマンドパターンを使用してコールバックを実装できます。これは、以下の最初のコード例で実際に動作していることがわかりますTestCommand.java
。
別の言語で関数ポインタを使用することに慣れている開発者Method
は、ReflectionAPIのオブジェクトを同じように使用したくなるかもしれません。たとえば、彼の記事「Java Reflection」では、Paul Tremblettが、switchステートメントを使用せずにReflectionを使用してトランザクションを実装する方法を示しています。私はこの誘惑に抵抗しました。なぜなら、Javaプログラミング言語にとってより自然な他のツールで十分な場合にSunはReflectionAPIを使用しないようにアドバイスしているからです。 (Tremblettの記事へのリンクおよびSunのReflectionチュートリアルページについては、「参考文献」を参照してください。)Method
オブジェクトを使用しない場合、プログラムのデバッグと保守が容易になります。代わりに、インターフェースを定義し、必要なアクションを実行するクラスに実装する必要があります。
したがって、コマンドパターンをJavaの動的ロードおよびバインディングメカニズムと組み合わせて使用して、関数ポインタを実装することをお勧めします。(Javaの動的ロードおよびバインディングメカニズムの詳細については、「参考文献」にリストされているJamesGoslingおよびHenryMcGiltonの「Java言語環境-ホワイトペーパー」を参照してください。)
上記の提案に従うことで、コマンドパターンの適用によって提供されるポリモーフィズムを活用して、巨大なswitchステートメントを排除し、拡張可能なシステムを実現します。また、Java独自の動的ロードおよびバインディングメカニズムを利用して、動的で動的に拡張可能なシステムを構築します。これは、以下の2番目のコードサンプル例で示されていますTestTransactionCommand.java
。
コマンドパターンは、リクエスト自体をオブジェクトに変換します。このオブジェクトは、他のオブジェクトと同じように保存して渡すことができます。このパターンの鍵は、Command
操作を実行するためのインターフェースを宣言するインターフェースです。最も単純な形式では、このインターフェイスには抽象的なexecute
操作が含まれています。各具象Command
クラスReceiver
は、をインスタンス変数として格納することにより、レシーバーとアクションのペアを指定します。execute()
リクエストを呼び出すためのメソッドのさまざまな実装を提供します。Receiver
リクエストを実行するために必要な知識を持っています。
以下の図1は、オブジェクトのSwitch
集合体を示していCommand
ます。それは持っているflipUp()
とflipDown()
、そのインターフェイスで操作。コマンドインターフェイスで実行操作を呼び出すためSwitch
、呼び出し元と呼ばれます。
具体的なコマンドは、コマンドインターフェイスの操作をLightOnCommand
実装しexecute
ます。適切なReceiver
オブジェクトの操作を呼び出す知識があります。この場合、アダプターとして機能します。アダプターという用語Command
は、具体的なオブジェクトが単純なコネクターであり、Invoker
とReceiver
を異なるインターフェースで接続することを意味します。
クライアントは、インスタンス化Invoker
、Receiver
および具体的なコマンドオブジェクト。
図2のシーケンス図は、オブジェクト間の相互作用を示しています。これは、を(およびそれが実行する要求)からCommand
切り離す方法を示しています。クライアントは、コンストラクターを適切なでパラメーター化することにより、具体的なコマンドを作成します。そして、それが保存さに。コールが所望実行する知識有するコンクリートコマンド、バックアップ動作を制御します。Invoker
Receiver
Receiver
Command
Invoker
Invoker
Action()
クライアント(リストのメインプログラム)は、具体的なCommand
オブジェクトを作成し、そのオブジェクトを設定しReceiver
ます。Invoker
オブジェクトとして、Switch
具体的なCommand
オブジェクトを格納します。Invoker
呼び出すことによって要求を発行execute
上Command
のオブジェクトを。具象Command
オブジェクトは、そのオブジェクトに対する操作を呼び出しReceiver
て、要求を実行します。
ここで重要な考え方は、具体的なコマンドレジスタ自身でいることであるInvoker
とInvoker
上のコマンドを実行し、戻ってそれを呼び出しますReceiver
。
コマンドパターンのサンプルコード
コマンドパターンを介して実現されるコールバックメカニズムを示す簡単な例を見てみましょう。
この例は、Fan
とを示していLight
ます。私たちの目的はSwitch
、オブジェクトをオンまたはオフにできるものを開発することです。私たちは、ことを確認Fan
し、Light
を意味し、異なるインタフェースを持っているSwitch
とは独立していることがありReceiver
インタフェースまたはそれがコード>レシーバのインターフェイスの知識を持ちません。この問題を解決するにSwitch
は、適切なコマンドを使用して各をパラメーター化する必要があります。明らかに、にSwitch
接続されLight
ているコマンドは、にSwitch
接続されているコマンドとは異なりFan
ます。これCommand
が機能するには、クラスが抽象的であるか、インターフェースである必要があります。
aのコンストラクターSwitch
が呼び出されると、適切なコマンドセットでパラメーター化されます。コマンドは、のプライベート変数として保存されますSwitch
。
ときflipUp()
やflipDown()
操作が呼ばれて、彼らは単に適切なコマンドを行いますexecute( )
。Switch
結果として何が起こるか全くわから持ちませんexecute( )
と呼ばれているが。
TestCommand.javaクラスFan {public void startRotate(){System.out.println( "ファンは回転しています"); } public void stopRotate(){System.out.println( "ファンが回転していません"); }} class Light {public void turnOn(){System.out.println( "Light is on"); } public void turnOff(){System.out.println( "ライトがオフです"); }} class Switch {private Command UpCommand、DownCommand; public Switch(Command Up、Command Down){UpCommand = Up; //具象コマンドはそれ自体を呼び出し元に登録しますDownCommand = Down; } void flipUp(){//呼び出し側は具象コマンドを呼び出し、受信側のUpCommandでコマンドを実行します。実行(); } void flipDown(){DownCommand。実行(); }}クラスLightOnCommandはCommand {private LightmyLight;を実装します。 public LightOnCommand(Light L){myLight = L;} public void execute(){myLight。オンにする( ); }}クラスLightOffCommandはCommand {private LightmyLight;を実装します。 public LightOffCommand(Light L){myLight = L; } public void execute(){myLight。消す( ); }}クラスFanOnCommandはコマンドを実装します{privateFan myFan; public FanOnCommand(Fan F){myFan = F; } public void execute(){myFan。 startRotate(); }}クラスFanOffCommandはコマンドを実装します{privateFan myFan; public FanOffCommand(Fan F){myFan = F; } public void execute(){myFan。 stopRotate(); }} public class TestCommand {public static void main(String [] args){Light testLight = new Light(); LightOnCommand testLOC = new LightOnCommand(testLight); LightOffCommand testLFC = new LightOffCommand(testLight); Switch testSwitch = new Switch(testLOC、testLFC); testSwitch.flipUp(); testSwitch.flipDown();ファンtestFan = new Fan(); FanOnCommand foc = new FanOnCommand(testFan); FanOffCommand ffc = new FanOffCommand(testFan); Switch ts = new Switch(foc、ffc); ts.flipUp(); ts.flipDown(); }} Command.java public interface Command {public abstract void execute(); }
-コマンド・パターンは完全に動作を呼び出すオブジェクトを分離することが、上記のコード例に通知(Switch )
-知識を有するものからそれを実行するために-Light
とFan
。これにより、多くの柔軟性が得られます。リクエストを発行するオブジェクトは、リクエストの発行方法のみを知っている必要があります。リクエストがどのように実行されるかを知る必要はありません。
トランザクションを実装するためのコマンドパターン
コマンドパターンは、アクションまたはトランザクションパターンとも呼ばれます。TCP / IPソケット接続を介してクライアントから配信されたトランザクションを受け入れて処理するサーバーについて考えてみましょう。これらのトランザクションは、コマンドとそれに続く0個以上の引数で構成されます。
開発者は、コマンドごとに大文字と小文字を区別するswitchステートメントを使用できます。Switch
コーディング中のステートメントの使用は、オブジェクト指向プロジェクトの設計段階での設計不良の兆候です。コマンドは、トランザクションをサポートするオブジェクト指向の方法を表し、この設計上の問題を解決するために使用できます。
プログラムのクライアントコードではTestTransactionCommand.java
、すべてのリクエストが汎用TransactionCommand
オブジェクトにカプセル化されています。TransactionCommand
コンストラクタは、クライアントによって作成され、それはに登録されていますCommandManager
。キューに入れられたリクエストはrunCommands()
、を呼び出すことでさまざまな時間に実行できます。これにより、柔軟性が大幅に向上します。また、コマンドを複合コマンドにアセンブルする機能も提供します。私も持っているCommandArgument
、CommandReceiver
とCommandManager
のクラスとサブクラスTransactionCommand
-つまり、AddCommand
とSubtractCommand
。以下は、これらの各クラスの説明です。
CommandArgument
コマンドの引数を格納するヘルパークラスです。任意の型の多数または可変数の引数を渡すタスクを単純化するように書き直すことができます。CommandReceiver
すべてのコマンド処理メソッドを実装し、シングルトンパターンとして実装されます。CommandManager
は呼び出し元でありSwitch
、前の例と同等です。汎用TransactionCommand
オブジェクトをそのプライベートmyCommand
変数に格納します。ときにrunCommands( )
呼び出され、それが呼び出してexecute( )
、適切なのTransactionCommand
オブジェクトを。
Javaでは、名前を含む文字列を指定してクラスの定義を検索できます。クラスのexecute ( )
操作ではTransactionCommand
、クラス名を計算し、それを実行中のシステムに動的にリンクします。つまり、クラスは必要に応じてオンザフライでロードされます。トランザクションコマンドサブクラスの名前として、文字列「Command」で連結されたコマンド名という命名規則を使用して、動的にロードできるようにします。
Class
によって返されるオブジェクトはnewInstance( )
、適切なタイプにキャストする必要があることに注意してください。これは、新しいクラスがインターフェイスを実装するか、コンパイル時にプログラムに認識されている既存のクラスをサブクラス化する必要があることを意味します。この場合、Command
インターフェースを実装しているので、これは問題ではありません。