2012-04-25 6 views
11

抽象依存性注入と開発生産性

過去数ヶ月のために私は、APIの抽象化とエンティティ/コンポーネント/スクリプティングシステムとC#ベースのゲームエンジンを軽量プログラミングされています。 XNA、SlimDXなどのゲーム開発プロセスを、Unityエンジンと同様のアーキテクチャを提供することで簡単に行うことが、アイディア全体の目的です。

デザインは、ほとんどのゲーム開発者が知っているように、あなたのコード全体にアクセスする必要が異なるサービスがたくさんあります

に挑戦します。多くの開発者は、例えば、レンダリングマネージャ(または作曲家)、シーン、グラフィックデバイス(DX)、ロガー、入力状態、ビューポート、ウィンドウなどを含むことができる。グローバルな静的インスタンス/シングルトンにはいくつかの代替アプローチがあります。 1つは、各クラスに、コンストラクタまたはコンストラクタ/プロパティ依存インジェクション(DI)を介してアクセスする必要のあるクラスのインスタンスを与えることです。もう1つは、StructureMapのObjectFactoryのようなグローバルサービスロケータを使用する方法です。 IoCコンテナ。

依存性の注入は、私は多くの理由のためにDIの道を行くことを選びました。最も明白なものは、インターフェイスに対してプログラミングすることによってテスト容易性があり、コンストラクタを介して提供されるすべてのクラスのすべての依存関係を持つため、テストコンテナが必要なサービスまたはそれらのモックをインスタンス化し、すべてのクラスがテストされます。 DI/IoCを行うもう一つの理由は、コードの可読性を高めることであるとは思いません。すべての異なるサービスをインスタンス化し、必要なサービスへの参照を使用してクラスを手動でインスタンス化する巨大な初期化プロセスは不要です。カーネル(NInject)/レジストリ(StructureMap)を設定すると、サービス実装が選択され、設定されるエンジン/ゲームの設定が簡単になります。

私の問題

    私は私が行うすべてのDI-道物事を行う方法を心配しているので
  • 私の生産性がダウン劇的になったインタフェースのためのインタフェースを作成していますように私は頻繁に感じ
  • 、素早く簡単な代わりにグローバルスタティック方法。
  • 場合によっては、例えば、実行時に新しいエンティティをインスタンス化するときは、インスタンスを作成するためにIoCコンテナ/カーネルにアクセスする必要があります。これにより、IoCコンテナ自体に依存関係が作成されます(SMのObjectFactory、Ninjectのカーネルのインスタンス)これは、最初に1つを使用する理由に逆行します。どのように解決できますか?抽象的なファクトリが気になりますが、コードを複雑にするだけです。
  • サービスの要件によっては、クラスのコンストラクタによっては非常に大きくなることがあり、IoCが使用されていない他のコンテキストではクラスが完全に役に立たなくなります。

基本的にDI/IoCを実行すると、生産性が大幅に低下し、場合によってはコードとアーキテクチャがさらに複雑になります。したがって、私はそれが私が従うべき道であるか、それとも古風なやり方をあきらめてやるかは不明です。私は何をすべきか、すべきではないかという1つの答えを探しているわけではありませんが、DIを使用するのが長期的にはグローバルな静的/シングルトンの方法、可能な長所と短所、 DIを扱う際に上記の私の問題に対する可能な解決策を示します。

答えて

19

旧式の方法に戻ってください。 私の答えは簡単です。 DIには、あなたが言及したすべての理由で多くの利点があります。

私は、多くの場合、私はあなたがこれをやっている場合は、サービス要件、いくつかのクラスコンストラクタによって Reused Abstractions Principle (RAP)

に違反する可能性があるインタフェース酒

ためのインタフェースを作成していますように感じます を非常に大きくすることができます。これは、IoCが使用されていない他の コンテキストでクラスを完全に無駄にします。 Single Reponsibility Principle:あなたのクラスのコンストラクタが大きすぎると複雑な場合

が、これはあなたが非常に重要な他の原則に違反していることをお見せするための最良の方法です。この場合は、コードを別のクラスに抽出してリファクタリングする必要があります。依存関係の数は約4です。

DIを実行するには、インタフェースを使用する必要はありません。オブジェクトへの依存関係を取得します。インタフェースを作成することは、テスト目的のために依存関係を代用できる必要があります。 依存のオブジェクトがある場合を除き:

  1. 簡単
  2. は、外部サブシステム(ファイルシステム など)に話をしない隔離する

あなたは抽象クラスとして、あなたの依存関係を作成することができ、またはあなたが代用したいメソッドが仮想である任意のクラス。しかし、インタフェースは、依存関係の最良の分離された方法を作成します。

実行時に新しいエンティティをインスタンス化する場合、 はインスタンスを作成するためにIoCコンテナ/カーネルにアクセスする必要があります。 これは、IoCコンテナ自体(NinjectのカーネルのインスタンスであるObjectFactory SM)に依存します。実際には となります。どうすれば 解決できますか?抽象的なファクトリが気になりますが、それだけに はコードを複雑にします。

IOCコンテナへの依存関係は、クライアントクラスに依存することは決してありません。 そして、彼らはする必要はありません。

最初に依存性注入を正しく使用するには、Composition Rootの概念を理解することが重要です。これは、コンテナを参照する必要がある唯一の場所です。この時点で、オブジェクトグラフ全体が構築されます。これを理解すると、クライアントにコンテナが必要ないことが分かります。各クライアントは依存関係が注入されるだけです。

new SomeBusinessObject(
    new SomethingChangedNotificationService(new EmailErrorHandler()), 
    new EmailErrorHandler(), 
    new MyDao(new EmailErrorHandler())); 

あなたはどのように知っている具体的な工場を作成することができます。 は、あなたがこのような多くの依存関係を持つオブジェクトを構築したいと言う:

は建設を容易にするために、あなたが従うことができ、他の多くの生成に関するパターンもあります。この構築:

public static class SomeBusinessObjectFactory 
{ 
    public static SomeBusinessObject Create() 
    { 
     return new SomeBusinessObject(
      new SomethingChangedNotificationService(new EmailErrorHandler()), 
      new EmailErrorHandler(), 
      new MyDao(new EmailErrorHandler())); 
    } 
} 

をし、このようにそれを使用します。

SomeBusinessObject bo = SomeBusinessObjectFactory.Create(); 

また、貧しいマンジを使用し、すべての引数を取らないコンストラクタを作成することができます:それは働いていたよう

public SomeBusinessObject() 
{ 
    var errorHandler = new EmailErrorHandler(); 
    var dao = new MyDao(errorHandler); 
    var notificationService = new SomethingChangedNotificationService(errorHandler); 
    Initialize(notificationService, errorHandler, dao); 
} 

protected void Initialize(
    INotificationService notifcationService, 
    IErrorHandler errorHandler, 
    MyDao dao) 
{ 
    this._NotificationService = notifcationService; 
    this._ErrorHandler = errorHandler; 
    this._Dao = dao; 
} 

そして、それはちょうどようだ:

貧者のDIを使用して
SomeBusinessObject bo = new SomeBusinessObject(); 

が考えられています既定の実装が外部のサードパーティのライブラリにある場合は悪いが、適切な既定の実装を行うと悪くなりません。

明らかに、すべてのDIコンテナ、オブジェクトビルダーおよび他のパターンがあります。

あなたが必要とするのは、あなたのオブジェクトの良い創造性のパターンを考えることだけです。オブジェクト自体は依存関係の作成方法に気を付けるべきではなく、実際には複雑になり、2種類のロジックを混在させます。だから私はDIを使用して信じていない生産性の損失を持つ必要があります。

オブジェクトにインスタンスを1つだけ注入できない特殊なケースがいくつかあります。寿命が一般的に短く、オンザフライのインスタンスが必要な場合。この場合、あなたは依存関係としてオブジェクトに工場を注入する必要があります

public interface IDataAccessFactory 
{ 
    TDao Create<TDao>(); 
} 

あなたはこのバージョンを気づくことができるとして、それは、様々なタイプを作成するためにIoCコンテナを利用することができますので、(IoCコンテナかかわらず、注意してください一般的なものであり私のクライアントにはまだ見えません)。

public class ConcreteDataAccessFactory : IDataAccessFactory 
{ 
    private readonly IocContainer _Container; 

    public ConcreteDataAccessFactory(IocContainer container) 
    { 
     this._Container = container; 
    } 

    public TDao Create<TDao>() 
    { 
     return (TDao)Activator.CreateInstance(typeof(TDao), 
      this._Container.Resolve<Dependency1>(), 
      this._Container.Resolve<Dependency2>()) 
    } 
} 

私は、これは、工場は、オブジェクトの新しいインスタンスを作成する必要があり、ちょうどコンテナが対象得るような新しいインスタンスを提供すると仮定していないことに注意することが重要である、私はIoCコンテナを持っていたにもかかわらず、アクチベーターを使用お知らせ異なる生涯(Singleton、ThreadLocalなど)で登録することができます。ただし、使用しているコンテナによっては、これらの工場を生成できるものもあります。しかし、オブジェクトが一時的な有効期間で登録されていることが確実であれば、それを解決するだけで済みます。

EDIT:抽象工場の依存関係を持つクラスを追加:

public class SomeOtherBusinessObject 
{ 
    private IDataAccessFactory _DataAccessFactory; 

    public SomeOtherBusinessObject(
     IDataAccessFactory dataAccessFactory, 
     INotificationService notifcationService, 
     IErrorHandler errorHandler) 
    { 
     this._DataAccessFactory = dataAccessFactory; 
    } 

    public void DoSomething() 
    { 
     for (int i = 0; i < 10; i++) 
     { 
      using (var dao = this._DataAccessFactory.Create<MyDao>()) 
      { 
       // work with dao 
       // Console.WriteLine(
       //  "Working with dao: " + dao.GetHashCode().ToString()); 
      } 
     } 
    } 
} 

は、基本的にDI/IoCのは劇的に私の生産性が遅くなり いくつかのケースでは、さらにコードやアーキテクチャ

マークを複雑にすることSeemanはこの件に関するすばらしいブログを書いて、質問に答えました: このような質問に対する私の最初の反応は、疎結合コードが理解しにくいということです。何よりも難しい?

Loose Coupling and the Big Picture

EDIT:あなたが使用しているが実際の依存関係を考慮すると最終的に私はいないすべてのオブジェクトと依存必要があることを指摘したいと思いますか、依存関係を注入する必要があり、最初に考慮してください。

何依存関係ですか?

  • アプリケーション設定
  • システムリソース(時計)
  • サードパーティのライブラリ
  • データベース
  • WCF /ネットワークサービス
  • 外部システム(ファイル/メール)

どれでも上記のオブジェクトまたは共同作業者のうち、あなたのコントロールから外れて、サイドEFを引き起こす可能性があります行動の善し悪しと違いがあり、テストするのが難しくなります。これらは、抽象化(クラス/インタフェース)を検討し、DIを使用する時代です。

依存関係はありませんが、本当にDIは必要ありませんか?

  • List<T>
  • のMemoryStream
  • 弦楽器/プリミティブ例えば必要な場合、上記単にnewキーワードを使用してインスタンス化することができるよう
  • リーフオブジェクト/ DTOの

オブジェクト。特定の理由がない限り、このような単純なオブジェクトにはDIを使用することを推奨しません。オブジェクトが完全に制御されているかどうか、またオブジェクトグラフや副作用が発生していないかどうかを検討してください。この場合、単にそれらを新しいものにしてください。

私はMark Seemanの投稿へのリンクを多く投稿しましたが、彼の書籍やブログの投稿を読むことをお勧めします。

+0

非常に良い返信、トピックの上に、ありがとう!あなたがリンクしている両方の記事を読んでいます。両方とも、情報の大きなソースであり、実際に私が気づいた他の多くの質問に答えました。 RAP違反とSRPは、私の場合に留意すべき良い事柄として際立っています。クライアントからIoCコンテナを隠すために抽象ファクトリを使用すると言いますが、抽象的なファクトリがIoCコンテナに依存するのは大丈夫ですか? – edvaldig

+1

ありがとう、私はこれがあなたを助けてくれてうれしいです、そして、私はそう言っています。これは、抽象的な工場がコンテナについて知ることは非常に容認しています。工場はコンポジットルートと同じレイヤーで動作し、ビジネスロジックを含み、単に具体的なインスタンスを管理するだけです。テスト環境では、Iocコンテナを使用しない場合もありますが、コンクリートタイプが異なるため、異なるファクトリが必要になります – Andre

+0

こんにちは、私は依存関係が何であるかを理解するための最後の編集を追加しました。シンプルで必要のないオブジェクトの依存関係管理を心配する必要があります。 – Andre