依存性注入または静的ファクトリーを使用する必要がありますか?


81

システムを設計するとき、他のモジュールで使用されているモジュール(ロギング、データベースアクセスなど)の問題にしばしば直面します。問題は、これらのコンポーネントを他のコンポーネントに提供する方法です。依存性の注入またはファクトリパターンの使用の2つの答えが考えられます。ただし、両方とも間違っているようです:

  • 工場はテストを困難にし、実装の簡単な交換を許可しません。また、依存関係を明示しません(たとえば、データベースを使用するメソッドを呼び出すメソッドを呼び出すメソッドを呼び出すという事実を知らないメソッドを調べています)。
  • Dependecyインジェクションは、コンストラクター引数リストを大幅に膨張させ、コード全体にいくつかの側面を塗りつけます。典型的な状況は、半分以上のクラスのコンストラクターがこのように見える場合です(....., LoggingProvider l, DbSessionProvider db, ExceptionFactory d, UserSession sess, Descriptions d)

問題がある典型的な状況を次に示します。データベースからロードされたエラー記述を使用する例外クラスがあり、ユーザーセッションオブジェクトにあるユーザー言語設定のパラメーターを持つクエリを使用します。したがって、新しい例外を作成するには、データベースセッションとユーザーセッションを必要とする説明が必要です。したがって、例外をスローする必要がある場合に備えて、これらすべてのオブジェクトをすべてのメソッドにドラッグする運命にあります。

このような問題にどのように取り組むのですか?


2
ファクトリーがすべての問題を解決できる場合、たぶん、ファクトリーをオブジェクトにインジェクトし、そこからLoggingProvider、DbSessionProvider、ExceptionFactory、UserSessionを取得できます。
ジョルジオ

1
メソッドへの「入力」が多すぎる場合、渡されるか注入されるかは、メソッド設計自体の問題です。どちらを使用する場合でも、メソッドのサイズを少し小さくすることをお勧めします(インジェクション
ビルK

ここでの解決策は、引数を減らすことではありません。代わりに、オブジェクト内のすべての作業を実行して利益をもたらす、より高いレベルのオブジェクトを構築する抽象化を構築します。
アレックス

回答:


74

依存性注入を使用しますが、コンストラクター引数リストが大きくなりすぎる場合は、Facade Serviceを使用してリファクタリングします。アイデアは、コンストラクタの引数のいくつかをグループ化し、新しい抽象化を導入することです。

たとえば、SessionEnvironmenta DBSessionProvider、the、UserSessionおよびloadedをカプセル化する新しい型を導入できますDescriptions。ただし、どの抽象化が最も意味があるかを知るには、プログラムの詳細を知る必要があります。

同様の質問がSOですでにここで尋ねられました


9
+1:コンストラクター引数をクラスにグループ化することは非常に良い考えだと思います。また、これらの引数をより意味のある構造に整理する必要があります。
ジョルジオ

5
しかし、結果が意味のある構造ではない場合、SRPに違反する複雑さを隠しています。この場合、クラスのリファクタリングを行う必要があります。
danidacar

1
@Giorgio私は、「コンストラクター引数をクラスにグループ化することは非常に良い考えである」という一般的な声明に同意しません。「このシナリオ」でこれを修飾する場合、それは異なります。
ティムタム

19

Dependecyインジェクションは、コンストラクター引数リストを大幅に膨張させ、コード全体にいくつかの側面を塗りつけます。

それから、DIを適切に理解しているようには見えません。アイデアは、ファクトリ内でオブジェクトのインスタンス化パターンを反転させることです。

特定の問題は、より一般的なOOPの問題のようです。オブジェクトが実行時に通常の人間が読めない例外をスローし、その例外をキャッチする最終的なtry / catchの前に何かを持って、その時点でセッション情報を使用して新しいきれいな例外をスローできないのはなぜですか?

もう1つのアプローチは、コンストラクターを介してオブジェクトに渡される例外ファクトリーを持つことです。代わりに、新しい例外をスローするの、クラス(例えば、工場の方法で投げることができますthrow PrettyExceptionFactory.createException(data)

ファクトリオブジェクト以外のオブジェクトでは、new演算子を使用しないでください。通常、例外は1つの特別なケースですが、あなたの場合は例外かもしれません!


1
パラメータリストが長くなりすぎるのは、依存関係の注入を使用しているためではなく、より多くの依存性の注入が必要なためです。
ジョルジオ

それが理由の1つである可能性があります-一般に、特定のパターン(Builderパターンなど)それを指示します。オブジェクトが他のオブジェクトをインスタンス化するためにコンストラクターにパラメーターを渡す場合、それはIoCの明らかなケースです。
ジョナサンリッチ

12

静的ファクトリーパターンの短所は既に十分にリストされていますが、依存性注入パターンの短所にはまったく同意しません。

その依存関係の注入では、各依存関係のコードを記述する必要がありますが、バグではなく機能です。これらの依存関係が本当に必要かどうかを考えさせ、疎結合を促進します。あなたの例では:

問題がある典型的な状況を次に示します。データベースからロードされたエラー記述を使用する例外クラスがあり、ユーザーセッションオブジェクトにあるユーザー言語設定のパラメーターを持つクエリを使用します。したがって、新しい例外を作成するには、データベースセッションとユーザーセッションを必要とする説明が必要です。したがって、例外をスローする必要がある場合に備えて、これらすべてのオブジェクトをすべてのメソッドにドラッグする運命にあります。

いいえ、あなたは運命ではありません。特定のユーザーセッションのエラーメッセージをローカライズするのはなぜビジネスロジックの責任ですか?将来、そのビジネスサービスをバッチプログラム(ユーザーセッションを持たない...)から使用したい場合はどうでしょうか。または、現在ログインしているユーザーではなく、スーパーバイザー(別の言語を好む可能性がある)にエラーメッセージを表示しない場合はどうなりますか?または、クライアントでビジネスロジックを再利用したい場合(データベースにアクセスできない...)?

明らかに、メッセージのローカライズは、これらのメッセージを誰が見るかに依存します。つまり、プレゼンテーション層の責任です。したがって、ビジネスサービスから通常の例外をスローします。この例外は、使用するメッセージソースでプレゼンテーションレイヤーの例外ハンドラーを検索できるメッセージ識別子を保持します。

そのようにして、3つの不要な依存関係(UserSession、ExceptionFactory、およびおそらく説明)を削除することができます。これにより、コードがよりシンプルで汎用性の高いものになります。

一般的に言えば、ユビキタスアクセスが必要なものにのみ静的ファクトリーを使用し、コードを実行したいすべての環境(ロギングなど)で使用できることが保証されています。それ以外の場合は、単純な古い依存性注入を使用します。


この。例外をスローするためにDBにアクセスする必要性がわかりません。
カレス

1

依存性注入を使用します。静的ファクトリーの使用は、Service Locatorアンチパターンの使用です。ここでマーティン・ファウラーの独創的な作品をご覧ください-http://martinfowler.com/articles/injection.html

コンストラクターの引数が大きくなりすぎて、DIコンテナーを使用していない場合は、インスタンス化のために独自のファクトリーを作成し、XMLまたは実装をインターフェイスにバインドすることで構成できるようにします。


5
Service Locatorはアンチパターンではありません。ファウラー自身が投稿したURLでそれを参照しています。Service Locatorパターンは(シングルトンが悪用されるのと同じ方法で-グローバル状態を抽象化するために)悪用される可能性がありますが、非常に有用なパターンです。
ジョナサンリッチ

1
知って興味深い。私はそれがアンチパターンと呼ばれることを常に聞いてきました。
サム

4
サービスロケーターを使用してグローバル状態を保存する場合、これはアンチパターンにすぎません。サービスロケーターは、インスタンス化後のステートレスオブジェクトであり、不変であることが望ましい。
ジョナサンリッチ

XMLはタイプセーフではありません。他のすべてのアプローチが失敗した後、私はそれを計画zと考えます
リチャードティングル

1

私も依存性注入に行きます。DIは、コンストラクターだけでなく、プロパティセッターでも実行されることに注意してください。たとえば、ロガーをプロパティとして挿入できます。

また、たとえば、ドメインロジックが実行時に必要とするものにコンストラクターパラメーターを保持する(コンストラクターを、クラスと実際のドメインの依存関係)、およびプロパティを介して他のヘルパークラスを注入する場合があります。

さらに先に進むとよいのは、多くの主要なフレームワークに実装されているアスペクト指向プログラムです。これにより、クラスのコンストラクターをインターセプト(または「アドバイス」してAspectJ用語を使用)し、特別な属性を指定して関連するプロパティを注入できます。


4
セッターを通じてDIを避けるのは、オブジェクトが完全に初期化されていない状態(コンストラクターとセッター呼び出しの間)に時間枠を導入するためです。または、言い換えれば、メソッドの呼び出し順序を導入し(Yの前にXを呼び出す必要があります)、可能な限り回避します。
RokL 14年

1
プロパティセッターを介したDIは、オプションの依存関係に最適です。ロギングが良い例です。ロギングが必要な場合はLoggerプロパティを設定し、そうでない場合は設定しないでください。
プレストン14

1

工場はテストを困難にし、実装の簡単な交換を許可しません。また、依存関係を明示しません(たとえば、データベースを使用するメソッドを呼び出すメソッドを呼び出すメソッドを呼び出すという事実を知らないメソッドを調べています)。

私はまったく同意しません。少なくとも一般的には。

単純な工場:

public IFoo GetIFoo()
{
    return new Foo();
}

単純な注入:

myDependencyInjector.Bind<IFoo>().To<Foo>();

どちらのスニペットは、同じ目的を果たす、彼らは間のリンクを設定IFooしてFoo。それ以外はすべて単なる構文です。

に変更FooするにADifferentFooは、どちらのコードサンプルでも同じくらいの労力が必要です。
依存性の注入により異なるバインディングを使用できると人々が主張するのを聞いたことがありますが、異なるファクトリを作成することについて同じ議論をすることができます。適切なバインディングの選択は、適切なファクトリの選択とまったく同じくらい複雑です。

ファクトリメソッドを使用すると、たとえばFoo一部の場所やADifferentFoo他の場所で使用できます。これを良いと呼ぶ人もいれば(必要な場合に便利)、これを悪いと呼ぶ人もいます(すべてを交換する際に半ばおろそかな仕事をすることができます)。
ただし、返さIFooれる単一のメソッドに固執して、常に単一のソースを使用する場合、このあいまいさを回避するのはそれほど難しくありません。足で自分を撃ちたくない場合は、装填された銃を持たないか、足に向けないようにしてください。


Dependecyインジェクションは、コンストラクター引数リストを大幅に膨張させ、コード全体にいくつかの側面を塗りつけます。

これが、次のように、コンストラクターで依存関係を明示的に取得することを好む理由です。

public MyService()
{
    _myFoo = DependencyFramework.Get<IFoo>();
}

引数プロ(コンストラクターの肥大化なし)を聞いたことがあり、引数con(コンストラクターを使用するとより多くのDIオートメーションが可能になります)を聞いたことがあります。

個人的には、コンストラクター引数を使用したい先輩に譲りましたが、VSのドロップダウンリスト(右上、現在のクラスのメソッドを参照する)に問題があり、メソッド名が見えなくなると気づきましたメソッドシグネチャの長さが画面より長い(=>肥大化したコンストラクター)。

技術的なレベルでは、どちらの方法でもかまいません。どちらのオプションでも、入力するのと同じくらいの労力がかかります。また、DIを使用しているため、通常はコンストラクタを手動で呼び出すことはありません。しかし、Visual Studio UIのバグにより、コンストラクターの引数を肥大化させないようにしています。


補足として、依存性注入とファクトリーは相互に排他的ではありません。依存関係を挿入する代わりに、依存関係を生成するファクトリーを挿入した場合があります(幸いなことに、NInjectを使用Func<IFoo>すると、実際のファクトリークラスを作成する必要がなくなります)。

この使用例はまれですが、存在します。


OPは静的ファクトリーについて尋ねますstackoverflow.com/questions/929021/…–
バシレフス

@Basilevs「問題は、これらのコンポーネントを他のコンポーネントにどのように提供するかです。」
アンソニーラトレッジ

1
@Basilevsサイドノート以外の私が言ったことはすべて、静的な工場にも当てはまります。あなたが具体的に指摘しようとしていることはわかりません。関連するリンクは何ですか?
フラット

ファクトリをインジェクトするそのようなユースケースの1つは、抽象HTTPリクエストクラスを考案し、GET、POST、PUT、PATCH、DELETEの5つの他の5つのポリモーフィックな子クラスを戦略化したケースでしょうか?あなたは、HTTPリクエストクラスに部分的に依拠することができるMVC型ルータ(と言っクラス階層を統合しようとするたびに使用されるHTTPメソッドを知ることができませんPSR-7 HTTPメッセージインタフェースは醜いです。。
アンソニー・ラトリッジ

@AnthonyRutledge:DIファクトリは2つのことを意味します(1)複数のメソッドを公開するクラス。これがあなたの言っていることだと思います。ただし、これは工場に固有のものではありません。いくつかのパブリックメソッドを持つビジネスロジッククラスか、いくつかのパブリックメソッドを持つファクトリかは、セマンティクスの問題であり、技術的な違いはありません。(2)ファクトリのDI固有のユースケースは、非ファクトリ依存関係が1回インスタンス化される(注入中)のに対し、ファクトリバージョンを使用して、実際の依存関係を後の段階で(および場合によっては複数回)インスタンス化できます。
フラット

0

このモックの例では、実行時にファクトリクラスを使用して、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.");
        }
    }
}

centralized内のMVCタイプセットアップのクライアントコードはindex.php、次のようになります(検証は省略されます)。

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)に変更する必要があります。


コメントなしの投票は役に立ちません。
アンソニーラトレッジ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.