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は、具体的なオブジェクトが単純なコネクターであり、InvokerReceiverを異なるインターフェースで接続することを意味します。

クライアントは、インスタンス化InvokerReceiverおよび具体的なコマンドオブジェクト。

図2のシーケンス図は、オブジェクト間の相互作用を示しています。これは、を(およびそれが実行する要求)からCommand切り離す方法を示しています。クライアントは、コンストラクターを適切なでパラメーター化することにより、具体的なコマンドを作成します。そして、それが保存さに。コールが所望実行する知識有するコンクリートコマンド、バックアップ動作を制御します。InvokerReceiverReceiverCommandInvokerInvokerAction()

クライアント(リストのメインプログラム)は、具体的なCommandオブジェクトを作成し、そのオブジェクトを設定しReceiverます。Invokerオブジェクトとして、Switch具体的なCommandオブジェクトを格納します。Invoker呼び出すことによって要求を発行executeCommandのオブジェクトを。具象Commandオブジェクトは、そのオブジェクトに対する操作を呼び出しReceiverて、要求を実行します。

ここで重要な考え方は、具体的なコマンドレジスタ自身でいることであるInvokerInvoker上のコマンドを実行し、戻ってそれを呼び出します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 )-知識を有するものからそれを実行するために-LightFan。これにより、多くの柔軟性が得られます。リクエストを発行するオブジェクトは、リクエストの発行方法のみを知っている必要があります。リクエストがどのように実行されるかを知る必要はありません。

トランザクションを実装するためのコマンドパターン

コマンドパターンは、アクションまたはトランザクションパターンとも呼ばれます。TCP / IPソケット接続を介してクライアントから配信されたトランザクションを受け入れて処理するサーバーについて考えてみましょう。これらのトランザクションは、コマンドとそれに続く0個以上の引数で構成されます。

開発者は、コマンドごとに大文字と小文字を区別するswitchステートメントを使用できます。Switchコーディング中のステートメントの使用は、オブジェクト指向プロジェクトの設計段階での設計不良の兆候です。コマンドは、トランザクションをサポートするオブジェクト指向の方法を表し、この設計上の問題を解決するために使用できます。

プログラムのクライアントコードではTestTransactionCommand.java、すべてのリクエストが汎用TransactionCommandオブジェクトにカプセル化されています。TransactionCommandコンストラクタは、クライアントによって作成され、それはに登録されていますCommandManager。キューに入れられたリクエストはrunCommands()、を呼び出すことでさまざまな時間に実行できます。これにより、柔軟性が大幅に向上します。また、コマンドを複合コマンドにアセンブルする機能も提供します。私も持っているCommandArgumentCommandReceiverCommandManagerのクラスとサブクラスTransactionCommand-つまり、AddCommandSubtractCommand。以下は、これらの各クラスの説明です。

  • CommandArgumentコマンドの引数を格納するヘルパークラスです。任意の型の多数または可変数の引数を渡すタスクを単純化するように書き直すことができます。

  • CommandReceiver すべてのコマンド処理メソッドを実装し、シングルトンパターンとして実装されます。

  • CommandManagerは呼び出し元でありSwitch、前の例と同等です。汎用TransactionCommandオブジェクトをそのプライベートmyCommand変数に格納します。ときにrunCommands( )呼び出され、それが呼び出してexecute( )、適切なのTransactionCommandオブジェクトを。

Javaでは、名前を含む文字列を指定してクラスの定義を検索できます。クラスのexecute ( )操作ではTransactionCommand、クラス名を計算し、それを実行中のシステムに動的にリンクします。つまり、クラスは必要に応じてオンザフライでロードされます。トランザクションコマンドサブクラスの名前として、文字列「Command」で連結されたコマンド名という命名規則を使用して、動的にロードできるようにします。

Classによって返されるオブジェクトはnewInstance( )、適切なタイプにキャストする必要があることに注意してください。これは、新しいクラスがインターフェイスを実装するか、コンパイル時にプログラムに認識されている既存のクラスをサブクラス化する必要があることを意味します。この場合、Commandインターフェースを実装しているので、これは問題ではありません。