web-dev-qa-db-ja.com

Chain of Commandパターンでの依存性注入

インターフェイスを実装するモジュールを使用して、Chain of Commandパターンを実装しました。

interface IRequestHandler
{
    public function handle(&$offset, &$tripData, $request = null);
    public function setSuccessor(OffsetRequestHandler $handler);
}

必要に応じて$offsetおよび$tripDataオブジェクトを変更するモジュールのチェーンを設定しました(コマンドパターンのチェーンではない正確ではない、しかし、それは仕事を成し遂げる:)

単体テストを書くプロセスを簡単にすることが可能であるときはいつでも、依存性注入の使用を開始したいと思います。私の質問は、各モジュールが異なるサービスのセットを必要とする場合、それをどのように処理するかです。私はAurynを使用して、各モジュールのすべての依存関係を自動的にインスタンス化させることができます(少なくとも、私ができることを願っています)が、理論的には必要ないはずです...

更新:

チェーンを構築するために、構成ファイルからクラスの名前を取得し、それらをインスタンス化して、キューに配置します。コントローラーは、各モジュールに必要な依存関係を認識していません。現時点ではありません。各モジュールが必要なオブジェクトを自分で作成するためです(これが私が変更したいものです)。各モジュールにコンテナーを挿入したくないので、これはサービスロケーターパターンに変わります。

更新2:

必要性:

外部リソースからデータのセットを取得し、いくつかのメトリックを取得するためにそれを処理する必要があります。ただし、データが破損している、バッチで送信されている、などの可能性があるため、スクリプトで(可能な限り)修正する必要があります。問題の種類は、データの元となる外部リソースによって異なります。そして、状況は動的です。つまり、新しいタイプの問題と新しい外部リソースが将来出現する可能性があります。

私の考え:

モジュールを作成します。各モジュールは特定の問題に対処します。キューにモジュールを配置し(モジュールの順序が重要な場合があるため)、着信データをパラメーターとして挿入して実行します。

解決策は SitePoint に関するこの記事に強く触発されています。

しかし、例のようにインスタンスを1つずつ作成する代わりに:

$firstHandler = new FirstHandler();
$secondHandler = new SecondHandler();
$thirdHandler = new ThirdHandler();

//the code below sets all successors through the first handler
$firstHandler->setSuccessor($secondHandler);
$secondHandler->setSuccessor($thirdHandler);

$result = $firstHandler->handle($request);

クラスの名前を構成ファイルからプルし、foreachループを使用してクラスを作成します。

$modulesQueue = $config->get("extensions");

foreach ( $modulesQueue as $moduleName ) {
    $moduleName = "extensions\\".$moduleName;
    $startModule->setSuccessor(new $moduleName());
}
// Starting the chain:
$ret = $startModule->handle($offset, $tripDataObject, $requestData);

問題:

そのようなクラスのインスタンスを作成した場合、各モジュールが挿入する必要があるものがわかりません(現時点では、DIが適用されていないため、何もない-各モジュールは必要なサービスをインスタンス化します)。したがって、ここでは2つのオプションが表示されます。モジュールの手動作成に切り替える(これはまったく柔軟性がない)か、Reflectionを使用して各モジュールに必要なものを見つけて提供するかのいずれかです。

3
george007

わかりましたので、中心的な問題は、コマンドハンドラーオブジェクトの作成を一般的に作成することですが、コンストラクターの注入により、各コマンドハンドラーに個別のパラメーターが適用されます。

これは、ハンドラクラスごとに対応するファクトリクラスを利用して、チェーンの構築プロセスを実際のコンストラクタ呼び出しから分離することで簡単に解決できます。各ファクトリコンストラクターはパラメーターなしのままですが、各ファクトリー内では、ハンドラーオブジェクトは必要なサービスが注入されて構築されます。

FirstHandlerFactoryの場合はFirstHandlerSecondHandlerFactoryの場合はSecondHandlerなどの厳密な命名規則を使用すると、結果のコードは次のようになります。

foreach ( $modulesQueue as $moduleName ) {
    $factoryName = "extensions\\".$moduleName."Factory";
    $startModule->setSuccessor((new $factoryName())->buildModule());
}

(シンプルにするために、このコードはコマンドのチェーンを設定しないことを修正していませんが、アイデアは理解できると思います。)

その場合、buildModule関数は次のようになります。

 class FirstHandlerFactory
 { 
     function buildModule()
     {
        return new FirstHandler(/* provide the individual services here */);
     }
 }

単体テストでは、ファクトリーを使用せずに、特定のテストに必要なだけ、モックサービスを注入しただけでハンドラーオブジェクトを作成できます。

このソリューションでは、DIフレームワークも、リフレクションメカニズムも必要ありません。

1
Doc Brown

Pure Dependency Injection が何か知っていますか?依存性注入では、依存性注入コンテナを使用する必要はありません。あなたはコンテナであるオーリンに言及します。

コンテナの使用に本質的に問題はありませんが、すべてのフレームワークと同様に、作成者は自分をコンテナに依存させたいと考えています。これをコードベースに許可すると、コードベースの記述言語が効果的に変更されます。このジョブをPHPジョブとして宣伝することはできません。これはPHP/Aurynのジョブです。次に、PHP/Aurynプログラマーを見つける必要があります。

むしろ、あなたがそれを含むことができるあなたの言語をコンテナに再定義させてください。コードベースには何も許可しないでください 構成ルート (うまくいけばこれはただのメイン)でも、Aurynが存在し、ほとんどのコードベースは通常のPHPプログラマー。

チェーンを構築するために、構成ファイルからクラスの名前を取得し、それらをインスタンス化して、キューに配置します。コントローラーは、各モジュールに必要な依存関係を認識していません。現時点ではありません。各モジュールが必要なオブジェクトを自分で作成するためです(これが私が変更したいものです)。各モジュールにコンテナーを挿入したくないので、これはサービスロケーターパターンに変わります。

これは、テスト/リファクタリングの問題のように聞こえます。ユニットテストは、遅く導入した場合に最も費用がかかり、最も効果が低くなります。したがって、これが簡単であるとは期待しないでください。うまくいくものがあるようですね。統合テストはありますか?何らかのテストを実施している場合は、ユニットテスト可能なコードにリファクタリングする方がはるかに簡単です。統合テストが遅いため、それはただ遅いだけです。

Aurynを初めて使用する場合は、それで動作するコードを最初から作成して練習することをお勧めします。コードベースにしたい理想的な形式にします。仕組みをご覧ください。

これで、既存のコードベースをリファクタリングし、Aurynを使用した経験があるため、目的のモデルができました。次に、コードベースの小さな部分を切り離します。それらのユニットテストを記述し、Aurynの責任の下でそれらの構造をもたらします。 Aurynがこれを困難にする場合、最初は純粋なDIソリューションを試してください。どちらの方法でも、クラスが独自の依存関係を構築しないようにするか、理由もなくこれを実行します。代わりに、それらを外部でビルドして渡します。少しずつ、コードベースを単体テスト可能なものに変換します。

ユニットテストの目的は、「すべてのクラスをテストする」ことではないことに注意してください。コードを読みやすくするためです。おそらく、実際の動作コードが含まれていない、いくつかの退屈なグルーコードがあります。それをテストしようとして自分を殺さないでください。そのコードを読んで、その動作を予測することができます。しかし、興味深い動作コードが混在している場合は、テスト可能なものに移動します。これを行うことを 謙虚なオブジェクトパターン と呼びます。それは人々がロジックをビューからプレゼンターに移すことを主張するのと同じです。

「興味深い」と主観的に思われる場合は、コードの構造について考えるのをやめて、初心者にとってどのように見えるかについて考えるようにお願いしています。何かがどのように使用されているか、それを理解するために読み取る必要のあるコードの範囲を示す単体テストを彼らに与え、それが何をすることが期待されるかを明確にします。編集する必要があります。テストを使用して、コードを読みやすくします。お願いします。

3
candied_orange