12

IOC の Service Locator はアンチパターンであるとよく読んでいます。

昨年、作業中のアプリケーションに IOC (具体的には Ninject) を導入しました。アプリはレガシーで、非常に大きく、断片化されています。クラスまたはクラスのチェーンを作成する方法はたくさんあります。Web フレームワーク (カスタム) によって作成されるものもあれば、nHibernate によって作成されるものもあります。変なところにたくさん散らかっているだけです。

さまざまなシナリオをどのように処理し、少なくとも ServiceLocatorish ではなく、さまざまな場所でさまざまなカーネルにならないようにするにはどうすればよいでしょうか (シングルトン、HttpRequest、スレッドなどのスコープが重要です)。

編集現在の SL パターンに至った経緯について、もう少し詳しく説明します。

実際には、複数のカーネルは必要ありません必要なのは 1 つだけです (実際、SL のため、静的なものは 1 つしかありません)。セットアップは次のとおりです。

1) 7 ~ 8 の異なるプロジェクト/アセンブリに Ninject モジュールがあります。アプリ (webapp) が起動すると、アセンブリ スキャンによってモジュールが収集され、カーネルに読み込まれ、Service Locator に配置されます。そのため、すべてがかなり高価です。

2) 構築に適したカスタム UI フレームワークがあります。それぞれがライフサイクルの一部として 1 ~ 10 個のタブ ページを構成する約 120 個のタブ付きフォームを考えてみてください。SL は 5 ~ 6 か所で戦略的に使用されており、純粋な解決として、またはプロパティのインスタンス化後の注入のみを行うことで、これらすべてをカバーしています。

3) UI の下にあるものは、これらのトップ レベルの呼び出しではカバーされません。これらのクラスが IOC を使用したい場合は、独自の戦略を考え出す必要があります。それぞれ独自の小さな世界であるさまざまな小さなフレームワークがあります。

したがって、私が読んだことからそれを行う理想的な方法は、IOCにアクセスする必要があるときはいつでもカーネルを注入することです...まあ、それで問題ありません。SLの使用は最小限に抑えています。

しかし、このカーネルはどこから入手したのでしょうか? どこでも新しいものを構築して登録したくありません。私が使用しているカーネルが、他のすべての人が使用しているのと同じスコープオブジェクトを保持していることを確認し、すべてのモジュール。その時点で、それが何であれ、Service Locator のように見えますよね?

このアプリは巨大で密結合であることにも注意してください。一度のリファクタリングに数か月を費やす余裕はありません。SL は、時間があるときにできる限り IOC に取り組むための良い方法のように思えました。

4

1 に答える 1

12

したがって、私が読んだことからそれを行う理想的な方法は、IOCにアクセスする必要があるときはいつでもカーネルを注入することです...まあ、それで問題ありません。SLの使用は最小限に抑えています。

いいえ、カーネルをビジネス クラスに注入することは最善の方法ではありません。より良い方法は、たとえばファクトリを作成するIFooFactory { IFoo Create(); }Func<IFoo>、これに新しいインスタンスを作成させることです。

このインターフェースの実装は複合ルートに入り、カーネルのインスタンスを取得し、カーネルを使用して解決を行います。これにより、クラスが特定のコンテナーから解放され、別のコンテナーを使用して別のプロジェクトでそれらを再利用できます。Func の場合、次のモジュールを使用できます。Ninject は Func (自動生成されたファクトリ) をサポートしていますか? Ninject 2.4 では、これがネイティブでサポートされます。


リファクタリングに関する限り、アプリケーションのソース コードを知らずに何が最善の方法であるかを伝えることはほとんど不可能です。おそらく機能するアプローチを提供できます。

長期的には、アプリケーション全体を適切な DI にリファクタリングする必要があると思います。非常に大規模なプロジェクト (30 ~ 40 人年) で私がかつて行ったことは、次のようなものでした。

複合ルートから開始し、オブジェクト ツリーをたどり、適切な DI を使用するようにクラスを次々と変更します。すべてのリーフに到達したら、他のサービスに依存しないすべてのサービスのリファクタリングを開始し、同じアプローチを使用してそれらのリーフで動作します。その後、既にリファクタリングされたサービスのみに依存するサービスを続行し、すべてのサービスがリファクタリングされるまで繰り返します。これらのすべてのステップを順番に実行できるため、コードが継続的に改善され、同時に新しい機能を追加することもできます。それまでの間、ServiceLocation は、できるだけ早く正しくすることに重点が置かれている限り、許容されます。

疑似コードの例:

Foo{ ServiceLocator.Get<Service1>(), new Bar() }
Bar{ ServiceLocator.Get<IService1>(), ServiceLocator.Get<IService2>(), new Baz() }
Baz{ ServiceLocator.Get<IService3>() }
Service1 { ServiceLocator.Get<Service3>() }
Service2 { ServiceLocator.Get<Service3>() }
Service3 { new SomeClass()}

ステップ 1 - ルート (Foo) を変更する

Foo{ ctor(IService1, IBar) }
Bar{ ServiceLocator.Get<IService1>(), ServiceLocator.Get<IService2>(), new Baz() }
Baz{ ServiceLocator.Get<IService3>() }
Service1 { ServiceLocator.Get<IService2>() }
Service2 { ServiceLocator.Get<IService3>() }
Service3 { new SomeClass()}

Bind<IBar>().To<Bar>();
Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>());

ステップ 2 - ルートの依存関係を変更する

Foo{ ctor(IService1, IBar) }
Bar{ ctor(IService1, IService2, IBaz) }
Baz{ ServiceLocator.Get<IService3>() }
Service1 { ServiceLocator.Get<Service2>() }
Service2 { ServiceLocator.Get<Service3>() }
Service3 { new SomeClass()}

Bind<IBar>().To<Bar>();
Bind<IBaz>().To<Baz>();
Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>());
Bind<IService2>().ToMethod(ctx => ServiceLocator.Get<IService2>());

ステップ 3 - 依存関係を変更し、リーフになるまで続行します

Foo{ ctor(IService1, IBar) }
Bar{ ctor(IService1, IService2, IBaz) }
Baz{ ctor(IService3) }
Service1 { ServiceLocator.Get<Service2>() }
Service2 { ServiceLocator.Get<Service3>() }
Service3 { new SomeClass() }

Bind<IBar>().To<Bar>();
Bind<IBaz>().To<Baz>();
Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>());
Bind<IService2>().ToMethod(ctx => ServiceLocator.Get<IService2>());
Bind<IService3>().ToMethod(ctx => ServiceLocator.Get<IService3>());

ステップ 4 - 他のサービスに依存しないサービスをリファクタリングする

Foo{ ctor(IService1, IBar) }
Bar{ ctor(IService1, IService2, IBaz) }
Baz{ ctor(IService3) }
Service1 { ServiceLocator.Get<Service2>() }
Service2 { ServiceLocator.Get<Service3>() }
Service3 { ctor(ISomeClass) }

Bind<IBar>().To<Bar>();
Bind<IBaz>().To<Baz>();
Bind<ISomeClass>().To<SomeClass>();
Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>());
Bind<IService2>().ToMethod(ctx => ServiceLocator.Get<IService2>());
Bind<IService3>().To<Service3>().InSingletonScope();

ステップ 5 - 次に、依存関係としてサービスをリファクタリングしただけのサービスに依存するものをリファクタリングします。

Foo{ ctor(IService1, IBar) }
Bar{ ctor(IService1, IService2, IBaz) }
Baz{ ctor(IService3) }
Service1 { ServiceLocator.Get<Service2>() }
Service2 { ctor(IService3) }
Service3 { ctor(ISomeClass) }

Bind<IBar>().To<Bar>();
Bind<IBaz>().To<Baz>();
Bind<ISomeClass>().To<SomeClass>();
Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>());
Bind<IService2>().To<Service2>().InSingletonScope();
Bind<IService3>().To<Service3>().InSingletonScope();

ステップ 6 - すべてのサービスがリファクタリングされるまで繰り返します。

Foo{ ctor(IService1, IBar) }
Bar{ ctor(IService1, IService2, IBaz) }
Baz{ ctor(IService3) }
Service1 { ctor(IService2) }
Service2 { ctor(IService3) }
Service3 { ctor(ISomeClass) }

Bind<IBar>().To<Bar>();
Bind<IBaz>().To<Baz>();
Bind<ISomeClass>().To<SomeClass>();
Bind<IService1>().To<Service1>().InSingletonScope();
Bind<IService2>().To<Service2>().InSingletonScope();
Bind<IService3>().To<Service3>().InSingletonScope();

おそらく、リファクタリングと一緒に規約ベースのコンテナー構成に切り替えたいと思うでしょう。この場合、リファクタリングされたすべてのクラスに属性を追加してそれらをマークし、すべてのリファクタリングが完了した後に再度削除します。規則では、この属性を使用して、リファクタリングされたすべてのクラスをフィルタリングできます。

于 2011-07-05T17:21:43.557 に答える