システムを設計するとき、他のモジュールで使用されている多数のモジュール(ログ、データベースアクセスなど)の問題にしばしば直面します。問題は、これらのコンポーネントを他のコンポーネントに提供する方法です。依存関係の注入またはファクトリパターンの使用の可能性は2つあります。しかし、どちらも間違っているようです:
(....., LoggingProvider l, DbSessionProvider db, ExceptionFactory d, UserSession sess, Descriptions d)
ここに私が問題を抱えている典型的な状況があります:データベースからロードされたエラーの説明を使用する例外クラスがあり、ユーザーセッションオブジェクトにあるユーザー言語設定のパラメーターを持つクエリを使用しています。新しい例外を作成するには、データベースセッションとユーザーセッションを必要とする説明が必要です。そのため、例外をスローする必要がある場合に備えて、すべてのメソッド間でこれらのすべてのオブジェクトをドラッグする運命にあります。
どのように私はそのような問題に取り組むのですか?
依存性注入を使用しますが、コンストラクタの引数リストが大きくなりすぎる場合は、必ず Facade Service を使用してリファクタリングしてください。アイデアは、いくつかのコンストラクター引数をグループ化して、新しい抽象化を導入することです。
たとえば、SessionEnvironment
、DBSessionProvider
、およびロードされたUserSession
をカプセル化する新しいタイプDescriptions
を導入できます。ただし、どの抽象化が最も意味があるかを知るには、プログラムの詳細を知っている必要があります。
同様の質問がすでに行われました ここではSO 。
依存性注入はコンストラクタの引数リストを大幅に膨らませ、コード全体のいくつかの側面を汚します。
それから、DIを適切に理解しているようには見えません。つまり、ファクトリ内でオブジェクトのインスタンス化パターンを反転させることです。
あなたの特定の問題はより一般的なようですOOP問題。オブジェクトが実行時に通常の人間が読めない例外をスローし、最後のtry /の前に何かができないのはなぜですか?その例外をキャッチし、その時点でセッション情報を使用して、新しいきれいな例外をスローしますか?
別のアプローチは、コンストラクターを介してオブジェクトに渡される例外ファクトリーを持つことです。新しい例外をスローする代わりに、クラスはファクトリのメソッド(throw PrettyExceptionFactory.createException(data)
など)をスローできます。
ファクトリオブジェクト以外のオブジェクトでは、new
演算子を使用しないでください。例外は一般的に1つの特別なケースですが、あなたのケースでは例外かもしれません!
静的ファクトリーパターンの短所はすでに十分に説明していますが、依存関係注入パターンの短所についてはあまり同意できません。
その依存関係の注入では、依存関係ごとにコードを記述する必要がありますが、これはバグではなく機能です。これらの依存関係が本当に必要かどうかを考えさせ、疎結合を促進します。あなたの例では:
ここに私が問題を抱えている典型的な状況があります:データベースからロードされたエラーの説明を使用する例外クラスがあり、ユーザーセッションオブジェクトにあるユーザー言語設定のパラメーターを持つクエリを使用しています。新しい例外を作成するには、データベースセッションとユーザーセッションを必要とする説明が必要です。そのため、例外をスローする必要がある場合に備えて、すべてのメソッド間でこれらのすべてのオブジェクトをドラッグする運命にあります。
いいえ、あなたは運命ではありません。特定のユーザーセッションのエラーメッセージをローカライズするのはなぜビジネスロジックの責任なのですか。将来いつか、バッチプログラム(ユーザーセッションがない...)からそのビジネスサービスを使用したい場合はどうなりますか?または、現在ログインしているユーザーではなく、上司(別の言語を好む可能性がある人)にエラーメッセージを表示しない場合はどうなりますか?または、クライアント(データベースにアクセスできない...)でビジネスロジックを再利用したい場合はどうなりますか?
明らかに、メッセージのローカライズは、誰がこれらのメッセージを見るかに依存します。つまり、それはプレゼンテーション層の責任です。したがって、ビジネスサービスから通常の例外をスローします。これらの例外は、偶然に使用するメッセージソースのプレゼンテーションレイヤーの例外ハンドラーを検索できるメッセージIDを運びます。
このようにして、3つの不要な依存関係(UserSession、ExceptionFactory、およびおそらく説明)を削除して、コードをより単純かつ多用途にすることができます。
一般的に言って、静的ファクトリーは、ユビキタスアクセスが必要なものにのみ使用し、コードを実行したいすべての環境(Loggingなど)で利用できることが保証されています。それ以外の場合は、私は単純な古い依存性注入を使用します。
工場ではテストが面倒になり、実装を簡単に入れ替えることができません。また、依存関係を明らかにしません(たとえば、データベースを使用するメソッドを呼び出すメソッドを呼び出すメソッドを呼び出すという事実に気づかずに、メソッドを調べています)。
私は全く同意しません。少なくとも一般的には。
単純なファクトリ:
public IFoo GetIFoo()
{
return new Foo();
}
簡単な注射:
myDependencyInjector.Bind<IFoo>().To<Foo>();
どちらのスニペットも同じ目的を果たし、IFoo
とFoo
の間にリンクを設定します。他のすべては単なる構文です。
Foo
をADifferentFoo
に変更するには、どちらのコードサンプルでも同じくらいの労力がかかります。
依存関係の注入によって異なるバインディングを使用できると人々が主張するのを聞いたことがありますが、異なるファクトリを作成することについて同じ議論をすることができます。適切なバインディングの選択は、適切なファクトリの選択とまったく同じくらい複雑です。
ファクトリメソッドを使用すると、たとえば一部の場所ではFoo
を使用し、他の場所ではADifferentFoo
を使用します。これを善と呼ぶ人(必要な場合に便利)、悪と呼ぶ人(すべてを置き換えるときに中途半端な仕事をする可能性があります)。
ただし、IFoo
を返す単一のメソッドを使用して常に単一のソースを取得する場合、このあいまいさを回避することはそれほど難しくありません。自分を足で撃ちたくない場合は、装填した銃を持たないか、足を狙わないようにしてください。
依存性注入はコンストラクタの引数リストを大幅に膨らませ、コード全体のいくつかの側面を汚します。
これが、次のように、コンストラクターで依存関係を明示的に取得することを好む理由です。
public MyService()
{
_myFoo = DependencyFramework.Get<IFoo>();
}
私は引数pro(コンストラクターの肥大化なし)を聞いたことがあります。引数con(コンストラクターを使用するとより多くのDI自動化が可能になる)を聞いたことがあります。
個人的には、コンストラクター引数を使用したい先輩に譲りましたが、VSのドロップダウンリスト(右上、現在のクラスのメソッドを参照する)の問題に気づきました。メソッドのシグネチャの数が私の画面より長い(=>肥大化したコンストラクタ)。
技術的なレベルでは、どちらの方法でもかまいません。どちらのオプションでも、入力するのに同じくらいの労力がかかります。また、DIを使用しているため、通常は手動でコンストラクターを呼び出すことはありません。しかし、Visual StudioのUIのバグにより、コンストラクターの引数が膨らまないようにしています。
補足として、依存性注入とファクトリーは相互に排他的ではありません。依存関係を挿入する代わりに、依存関係を生成するファクトリを挿入した場合がありました(NInjectを使用するとFunc<IFoo>
したがって、実際のファクトリクラスを作成する必要はありません)。
この使用例はまれですが、実際には存在します。
依存性注入を使用します。静的ファクトリーの使用はService Locator
アンチパターン。マーティンファウラーの精力的な作品をこちらでご覧ください- http://martinfowler.com/articles/injection.html
コンストラクターの引数が大きくなりすぎて、DIコンテナーを使用していない場合は、インスタンス化のために独自のファクトリーを作成し、XMLまたは実装をインターフェースにバインドすることで構成できるようにします。
Dependency Injectionも同様です。 DIはコンストラクターだけでなく、プロパティセッターを通じても行われることに注意してください。たとえば、ロガーをプロパティとして挿入できます。
また、ドメインロジックによって実行時に必要なものにコンストラクターパラメーターを保持する(コンストラクターの意図を明らかにする方法でコンストラクターを保持するなど)ことで、負担の一部を解消できるIoCコンテナーを使用することもできます。クラスと実際のドメインの依存関係)、およびプロパティを介して他のヘルパークラスを挿入することもできます。
さらに進んだ方がいいかもしれない一歩は、アスペクト指向のプログラムであり、多くの主要なフレームワークに実装されています。これにより、クラスのコンストラクターをインターセプト(またはAspectJ用語を使用するように「アドバイス」)し、関連するプロパティを注入することができます。特殊な属性が指定されている場合があります。
このモックの例では、ファクトリクラスを実行時に使用して、HTTPリクエストメソッドに基づいて、インスタンス化するインバウンドHTTPリクエストオブジェクトの種類を決定します。ファクトリー自体には、依存関係注入コンテナーのインスタンスが注入されます。これにより、ファクトリはランタイムを決定し、依存関係注入コンテナに依存関係を処理させることができます。各インバウンドHTTPリクエストオブジェクトには、少なくとも4つの依存関係(スーパーグローバルおよびその他のオブジェクト)があります。
<?php
namespace TFWD\Factories;
/**
* A class responsible for instantiating
* InboundHttpRequest objects (PHP 7.x)
*
* @author Anthony E. Rutledge
* @version 2.0
*/
class InboundHttpRequestFactory
{
private const GET = 'GET';
private const POST = 'POST';
private const PUT = 'PUT';
private const PATCH = 'PATCH';
private const DELETE = 'DELETE';
private static $di;
private static $method;
// public function __construct(Injector $di, Validator $httpRequestValidator)
// {
// $this->di = $di;
// $this->method = $httpRequestValidator->getMethod();
// }
public static function setInjector(Injector $di)
{
self::$di = $di;
}
public static setMethod(string $method)
{
self::$method = $method;
}
public static function getRequest()
{
if (self::$method == self::GET) {
return self::$di->get('InboundGetHttpRequest');
} elseif ((self::$method == self::POST) && empty($_FILES)) {
return self::$di->get('InboundPostHttpRequest');
} elseif (self::$method == self::POST) {
return self::$di->get('InboundFilePostHttpRequest');
} elseif (self::$method == self::PUT) {
return self::$di->get('InboundPutHttpRequest');
} elseif (self::$method == self::PATCH) {
return self::$di->get('InboundPatchHttpRequest');
} elseif (self::$method == self::DELETE) {
return self::$di->get('InboundDeleteHttpRequest');
} else {
throw new \RuntimeException("Unexpected HTTP request. Invalid request.");
}
}
}
集中型index.php
内のMVCタイプセットアップのクライアントコードは、次のようになります(検証は省略されます)。
InboundHttpRequestFactory::setInjector($di);
InboundHttpRequestFactory::setMethod($httpRequestValidator->getMethod());
$di->set('InboundHttpRequest', InboundHttpRequestFactory::getRequest());
$router = $di->get('Router'); // The Router class depends on InboundHttpRequest objects.
$router->dispatch();
あるいは、ファクトリーの静的な性質(およびキーワード)を削除し、依存関係インジェクターが全体を管理できるようにすることができます(したがって、コメント化されたコンストラクター)。ただし、クラスメンバー参照(self
)の一部(定数ではない)をインスタンスメンバー($this
)に変更する必要があります。