3

Ninjectに慣れようとしています。依存性注入の原則を理解し、Ninject の使用方法を知っています。しかし、私は今少し混乱しています。Service Locator パターンに関しては、意見が分かれています。

私のアプリケーションは厳密にモジュール化されています。私はできる限りコンストラクター注入を使用しようとしていますが、これはかなりうまく機能しますが、少し面倒です (私の意見では)。

拡張機能 (外部コード) がこのシステムの恩恵を受けたい場合、カーネルにアクセスする必要があるのではないでしょうか? つまり、現在、アプリケーションのすべてのサブシステムにアクセスできる静的クラスが 1 つあります。別の方法として、カーネル (Service Locator パターン) にアクセスし、そこからサブシステムの依存関係を取得します。ここでは、カーネルへのアクセスを許可することを簡単に回避したり、カーネルへの依存関係を許可しないことで、より明示的にしたりできます。

しかし、拡張機能が私のアプリケーションのコンポーネント (インターフェース) を任意のサブシステムから使用したい場合、それらを解決するためにカーネルにアクセスする必要があります。これは、使用していない限り Ninject が自動的に解決されないためです。 「kernel.Get()」ですよね?

Peww、これをわかりやすい方法で説明するのは本当に難しいです。皆さんが私が目指しているものを手に入れることを願っています。

カーネルまたはそのラッパーに依存することが「悪い」のはなぜですか? つまり、すべての依存関係を回避することはできません。たとえば、すべてのサブシステムへのアクセスを提供する「コア」クラスへのクラスがまだあります。拡張機能が独自のモジュールをさらに使用するために登録したい場合はどうなりますか?

なぜこれが悪いアプローチである必要があるのか​​ についての良い答えは見つかりませんが、かなり頻繁に読んでいます。さらに、Ninject は Unity や類似のフレームワークとは異なり、このアプローチを使用しないと述べられています。

ありがとう :)

4

2 に答える 2

4

これについては宗教戦争があります...

Service Locator について言及したときに人々が最初に言うことは、「でも、コンテナーを変更したい場合はどうすればよいですか?」ということです。「適切な」Service Locator は、基礎となるコンテナーを切り替えることができるほど抽象的である可能性があるため、この引数はほとんどの場合無効です。

とはいえ、私の経験では、Service Locator を使用すると、コードの操作が難しくなります。Service Locator はどこにでも渡す必要があり、コードはService Locator の存在そのものと密接に結合されます。

Service Locator を使用する場合、モジュールを「分離」(ここでは大まかに使用) する方法で維持するための 2 つの主なオプションがあります。

オプション1:

ロケーターを必要とするすべてのものにロケーターを渡します。本質的に、これはあなたのコードがこの種のコードの多くになることを意味します:

var locator = _locator;

var customerService = locator.Get<ICustomerService>();
var orders = customerService.GetOrders(locator, customerId); // locator again

// .. further down..
var repo = locator.Get<ICustomerRepository>();
var orderRepo = locator.Get<IOrderRepository>();

// ...etc...

オプション 2:

すべてのコードを 1 つのアセンブリに分割し、public staticどこかに Service Locator を提供します。これはさらに悪いことであり、最終的に上記と同じになります (Service Locator への直接呼び出しのみ)。

Ninject は、システムのほぼすべての部分で制御の反転を完全に利用できるようにする拡張機能のヒープを持っているという点で幸運です (つまり、Remo には優れたメンテナー/エクステンダーがいます)。その上。

于 2014-09-04T00:32:18.413 に答える
2

これは SO のポリシーに少し反しますが、Simon の回答を拡張するために、Mark Seeman の優れたブログ投稿を紹介します : http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/ブログ投稿へのコメントも非常に興味深いものです。

ninject と拡張機能に関する問題に対処するために、それらを作成した時点であなた/コンポジション ルートには知られていないと思いますNinjectModule-s を指摘したいと思います。Ninject はすでに、すべての dll で頻繁に使用される拡張メカニズムを備えていninject.extension.XYZます。

あなたがすることはFooExtensionModule : NinjectModule、拡張機能にいくつかのクラスを実装することです。モジュールにはBindメソッドが含まれています。次に、いくつかの .dll からすべてのモジュールをロードするように ninject に指示します。それでおしまい。

ここでは、はるかに詳細に説明されています: https://github.com/ninject/ninject/wiki/Modules-and-the-Kernel

欠点:

  • 拡張機能は Ninject に依存します
    • 「ninjectを更新する」ときに拡張機能を再コンパイルする必要がある場合があります
    • ソフトウェアが成長するにつれて、DI コンテナーを切り替えるコストがますます高くなります。
  • Rebind追跡が困難な問題が発生する可能性があります(これは、モジュールを使用しているかどうかに関係なく当てはまります。)
  • 特に、拡張機能の開発者が他の拡張機能について知らない場合、同一または競合するバインディング ( や など) を作成する可能性があり.Bind<string>().ToConst("Foo")ます.Bind<string>().ToConst("Bar")繰り返しますが、これはモジュールを使用していない場合にも当てはまりますが、拡張機能によってさらに複雑なレイヤーが追加されます。

利点: - シンプルで要点を言えば、コンテナを抽象化するために必要な複雑化/抽象化の余分なレイヤーはありません。

私はNinjectModule、それほど小さくないアプリケーション (15k ユニッ​​ト/コンポーネント テスト) でこのアプローチを使用して、多くの成功を収めました。

スコープなしなどの単純なバインディングだけが必要な場合は.Bind<IFoo>().To<Foo>()、クラスに属性を配置し、これらをスキャンし、コンポジション ルートでバインディングを作成するなど、より単純なシステムの使用を検討することもできます。このアプローチはそれほど強力ではありませんが、そのため、はるかに「移植性」が高くなります (他の DI コンテナーでの使用に適応します)。

依存性注入の遅いインスタンス化

コンポジション ルートの考え方は、(可能な限り) すべてのオブジェクト (オブジェクト グラフ全体) を一度に作成することです。たとえば、Mainメソッドでkernel.Get<IMainViewModel>().Show().

ただし、これが実行できない場合や適切でない場合もあります。そのような場合、ファクトリを使用する必要があります。実際、stackoverflow には、この点に関する多くの回答があります。次の 3 つの基本的なタイプがあります。

  • and (注入された ctor)Fooのインスタンスを必要とするインスタンスを作成するには、注入されたandの 1 つのインスタンスを取得するクラスを作成します。その後、メソッドは実行します。Dependency1Dependency2FooFactoryDependency1Dependency2FooFactory.Createnew Foo(this.dependency1, this.dependency2)
  • ninject.extensions.Factoryを使用します:
    • Func<Foo>ファクトリとして使用: をFunc<Foo>注入し、それを呼び出して のインスタンスを作成できますFoo
    • インターフェイスと.ToFactory()バインディングを使用します (このアプローチをお勧めします。よりクリーンなコード、より優れたテスト容易性)。例: IFooFactorywith method Foo Create(). 次のようにバインドします。Bind<IFooFactory>().ToFactory();

実装を置き換える拡張機能

私見、これは依存性注入コンテナーの目標の 1 つではありません。それが不可能だという意味ではありませんが、自分で解決しなければならないということです。

ninject を使用する最も簡単な方法は、 を使用すること.Rebind<IFoo>().To<SomeExtensionsFoo>()です。ただし、前述のように、それは少しもろいです。Bindとが間違っRebindた順序で実行されると、失敗します。複数Rebindの がある場合、最後のものが勝ちますが、それは正しいものですか?

それでは、さらに一歩進めましょう。想像:

`.Bind<IFoo>().To<SomeExtensionsFoo>().WhenExtensionEnabled<SomeExtension>();`

構文メソッドを拡張する独自のカスタムWhenExtensionEnabled<TExtension>()拡張メソッドを考案できます。When(Func<bool> condition)拡張機能が有効かどうかを検出する方法を工夫する必要があります。

于 2014-09-04T04:50:33.373 に答える