コマンド指向インターフェース
2003年11月23日
モジュールへのインターフェースの最も一般的なスタイルは、プロシージャ、またはオブジェクトメソッドを使用することです。そのため、契約に対して多数の料金を計算するモジュールが必要な場合は、計算を行うためのメソッドを持つBillingServiceクラスを用意し、次のように呼び出すことができます。
aBillingService.calculateCharges(aContract)
コマンド指向インターフェースは、各操作に対してコマンドクラスを持ち、次のようなもので呼び出されます。
CalculateChargeCommand.new(aContract).run()
基本的に、メソッド指向インターフェースにあるメソッドごとに1つのコマンドクラスがあります。
一般的なバリエーションとして、コマンドの実行を実際に行う、独立したコマンド実行オブジェクトを持つことです。
aCommandExecutor.run(CalculateChargeCommand.new(aContract))
Strutsのようなフレームワークを使用したことがある場合は、アクションクラスがこの操作スタイルに従っていることがわかるでしょう。
では、なぜ人々はこのアプローチを好きになったり、嫌いになったりするのでしょうか?まず、コマンド指向インターフェースはメソッド指向インターフェースよりもかなり複雑であると言えます。コマンドをインスタンス化し、実行のために渡す必要があります。これは、メソッドを呼び出すよりも複雑です。そのため、このアプローチのファンでさえ、重要なインターフェース(サービ スレイヤー、サーバーサイドロジック、主要なサブシステムのインターフェース)にのみ使用します。
コマンド指向インターフェースには、多くの利点があります。主な利点の1つは、コマンドエグゼキュータをデコレートすることで、コマンドに共通の動作を簡単に追加できることです。これは、トランザクション、ロギングなどを処理するのに非常に便利です。コマンドは後で実行するためにキューに入れられ、(コマンドとそのデータがシリアライズ可能な場合)ネットワーク経由で渡すことができます。コマンドの結果は、コマンド名と引数から合成されたキーに対して結果を保持することでキャッシュできます。
コマンドに関する不満を見たことがある場合、最大の問題は、コマンドに多くの重複したロジックがあることです。これは、コマンドが大量のロジックを含むトランザクションスクリプトである場合に最も頻繁に発生します。これは必ずしもコマンドを使用することとメソッドを使用することの問題ではなく、トランザクションスクリプトの一般的な問題です。コマンド指向の構造がこの問題を誇張する傾向があるのかもしれません。それは、多くの人がクラスには1~2ページのコードが必要だと感じ、コマンドに本来あるべき以上のコードを入れてしまうからです。
このページではインターフェースという言葉を使用していることに気付くでしょう。これは、コマンドを使用するかどうかの選択は、コマンドロジックの実際の実装ではなく、クライアントへのインターフェースに関するものであることを反映しています。実行メソッドが別のメソッドを呼び出すだけの1行であるコマンドクラスを持つことは、 perfectly reasonableです。これを行うことで、コマンドのすべての利点が得られますが、ロジックをコマンドクラス自体から分離しておくことができます。このようなコマンドクラスのコード行数は非常に少なくなります。
コマンドに関するよくある質問は、何を返すかということです。汎用的な実行メソッドには、ObjectやCommandResultなどの汎用的な戻り値の型が必要ですが、コマンドの実行結果を取得するには、より具体的な型が必要です。1つの方法は、各コマンドクラスの結果クラスを定義し、命名規則を使用することです。そのため、CalculateChargeCommandの戻り値の型はCalculateChargeResultになります。もう1つの方法は、コマンドに同じオブジェクトに結果を格納させることです。この場合、最初にコマンドを実行し、次に結果についてコマンドオブジェクトに問い合わせます。