36

私が取り組んでいるプロジェクトがそれから大きな恩恵を受けると感じているので、私は .NET のさまざまな依存性注入フレームワークを見てきました。私はこれらのフレームワークの機能をよく理解していると思いますが、それらを大規模なシステムに導入する最善の方法についてはまだ少し不明確です. ほとんどのデモは (当然のことながら) 1 つまたは 2 つの依存関係を持つ非常に単純なクラスになる傾向があります。

3つ質問があります...

まず、ILog、IApplicationSettings、IPermissions、IAudit など、一般的だが興味のない依存関係をどのように処理しますか。すべてのクラスがこれらをコンストラクターのパラメーターとして持つのはやり過ぎのようです。DI コンテナーの静的インスタンスを使用して、必要なときにこれらを取得する方がよいでしょうか?

MyClass(ILog log, IAudit audit, IPermissions permissions, IApplicationSettings settings)
// ... versus ...
ILog log = DIContainer.Get<ILog>();

第二に、使用される可能性はあるが作成に費用がかかる可能性がある依存関係にどのようにアプローチしますか。例 - クラスが ICDBurner インターフェイスに依存しているが、CD 書き込み機能が実際に使用されていない限り、具体的な実装を作成したくない場合があります。コンストラクターでファクトリ (ICDBurnerFactory など) にインターフェイスを渡しますか? それとも、DI コンテナーに直接アクセスする静的な方法を使用し、必要な時点でそれを要求しますか?

第 3に、最上位の GUI コンポーネント (MainForm など) が潜在的に数百のサブパネルまたはモーダル フォームの親であり、それぞれにいくつかの依存関係がある大規模な Windows フォーム アプリケーションがあるとします。これは、 MainForm がその子のすべての依存関係のスーパーセットを依存関係として持つように設定する必要があることを意味しますか? そして、そうすると、MainForm を作成する瞬間に必要となる可能性のあるすべてのクラスを構築する巨大な自己膨張モンスターが作成され、その過程で時間とメモリが無駄になるのではないでしょうか?

4

6 に答える 6

6

最初:必要に応じて、単純な依存関係をコンストラクターに追加します。すべてのコンストラクターにすべての型を追加する必要はありません。必要なものを追加するだけです。別のものが必要です。コンストラクターを展開するだけです。これらのタイプのほとんどはシングルトンである可能性が高く、最初の呼び出し後にすでに作成されているため、パフォーマンスは大きな問題ではありません。静的 DI コンテナーを使用して他のオブジェクトを作成しないでください。代わりに、DI コンテナーをそれ自体に追加して、それ自体を依存関係として解決できるようにします。このようなものです(現時点ではUnityを想定しています)

IUnityContainer container = new UnityContainer();
container.RegisterInstance<IUnityContainer>(container);

このようにして、IUnityContainer に依存関係を追加し、それを使用して高価なオブジェクトやほとんど必要とされないオブジェクトを作成できます。主な利点は、静的な依存関係がないため、単体テストがはるかに簡単になることです。

2 番目:ファクトリ クラスを渡す必要はありません。上記の手法を使用すると、必要に応じて DI コンテナー自体を使用して高価なオブジェクトを作成できます。

3: DI コンテナーと軽いシングルトンの依存関係をメイン フォームに追加し、必要に応じて DI コンテナーを介して残りを作成します。もう少しコードが必要ですが、あなたが言ったように、起動時にすべてを作成すると、メインフォームの起動コストとメモリ消費が屋根を通り抜けます。

于 2008-10-13T20:27:24.120 に答える
4

「作成に費用がかかり、使用される可能性がある」に関連する同様のケースがあり、独自の IoC 実装で、ファクトリ サービスの自動サポートを追加しています。

基本的に、これの代わりに:

public SomeService(ICDBurner burner)
{
}

あなたはこれをするでしょう:

public SomeService(IServiceFactory<ICDBurner> burnerFactory)
{
}

ICDBurner burner = burnerFactory.Create();

これには 2 つの利点があります。

  • バックグラウンドで、サービスを解決したサービス コンテナーは、要求された場合にバーナーを解決するためにも使用されます。
  • これにより、サービス コンテナー自体をパラメーターとしてサービスに挿入するのが一般的な方法である、この種のケースで以前に見た懸念が軽減されます。どちらか教えて」

ファクトリ オブジェクトは比較的簡単に作成でき、多くの問題を解決します。

これが私のファクトリークラスです:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LVK.IoC.Interfaces;
using System.Diagnostics;

namespace LVK.IoC
{
    /// <summary>
    /// This class is used to implement <see cref="IServiceFactory{T}"/> for all
    /// services automatically.
    /// </summary>
    [DebuggerDisplay("AutoServiceFactory (Type={typeof(T)}, Policy={Policy})")]
    internal class AutoServiceFactory<T> : ServiceBase, IServiceFactory<T>
    {
        #region Private Fields

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private readonly String _Policy;

        #endregion

        #region Construction & Destruction

        /// <summary>
        /// Initializes a new instance of the <see cref="AutoServiceFactory&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="serviceContainer">The service container involved.</param>
        /// <param name="policy">The policy to use when resolving the service.</param>
        /// <exception cref="ArgumentNullException"><paramref name="serviceContainer"/> is <c>null</c>.</exception>
        public AutoServiceFactory(IServiceContainer serviceContainer, String policy)
            : base(serviceContainer)
        {
            _Policy = policy;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AutoServiceFactory&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="serviceContainer">The service container involved.</param>
        /// <exception cref="ArgumentNullException"><paramref name="serviceContainer"/> is <c>null</c>.</exception>
        public AutoServiceFactory(IServiceContainer serviceContainer)
            : this(serviceContainer, null)
        {
            // Do nothing here
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the policy that will be used when the service is resolved.
        /// </summary>
        public String Policy
        {
            get
            {
                return _Policy;
            }
        }

        #endregion

        #region IServiceFactory<T> Members

        /// <summary>
        /// Constructs a new service of the correct type and returns it.
        /// </summary>
        /// <returns>The created service.</returns>
        public IService<T> Create()
        {
            return MyServiceContainer.Resolve<T>(_Policy);
        }

        #endregion
    }
}

基本的に、サービス コンテナー ビルダー クラスからサービス コンテナーをビルドすると、すべてのサービス登録に別の共同サービスが自動的に与えられ、そのサービスの IServiceFactory が実装されます。上記のサービスは、ポリシーを指定する 1 つのパラメーター (ポリシーが使用されていない場合は null になる可能性があります) と共に使用されます。

これにより、次のことが可能になります。

var builder = new ServiceContainerBuilder();
builder.Register<ISomeService>()
    .From.ConcreteType<SomeService>();

using (var container = builder.Build())
{
    using (var factory = container.Resolve<IServiceFactory<ISomeService>>())
    {
        using (var service = factory.Instance.Create())
        {
            service.Instance.DoSomethingAwesomeHere();
        }
    }
}

もちろん、より一般的な用途は CD Burner オブジェクトです。上記のコードでは、もちろん代わりにサービスを解決しますが、これは何が起こるかを示しています。

そのため、代わりに CD バーナー サービスを使用します。

var builder = new ServiceContainerBuilder();
builder.Register<ICDBurner>()
    .From.ConcreteType<CDBurner>();
builder.Register<ISomeService>()
    .From.ConcreteType<SomeService>(); // constructor used in the top of answer

using (var container = builder.Build())
{
    using (var service = container.Resolve<ISomeService>())
    {
        service.Instance.DoSomethingHere();
    }
}

サービス内で、要求に応じて CD バーナー サービスを解決する方法を知っているサービス、ファクトリ サービスを使用できるようになりました。これは、次の理由で役立ちます。

  • 同時に複数のサービスを解決したい場合があります (2 枚のディスクを同時に書き込みますか?)。
  • 必要ない場合もあれば、作成にコストがかかる場合もあるため、必要な場合にのみ解決します
  • 既存のサービス インスタンスをクリーンアップすることを期待/試行する代わりに、解決、破棄、解決、破棄を複数回行う必要がある場合があります。
  • また、コンストラクターで、必要なサービスと必要可能性があるサービスにフラグを立てています

以下は同時に2つです。

using (var service1 = container.Resolve<ISomeService>())
using (var service2 = container.Resolve<ISomeService>())
{
    service1.Instance.DoSomethingHere();
    service2.Instance.DoSomethingHere();
}

同じサービスを再利用するのではなく、次の 2 つの例を次に示します。

using (var service = container.Resolve<ISomeService>())
{
    service.Instance.DoSomethingHere();
}
using (var service = container.Resolve<ISomeService>())
{
    service.Instance.DoSomethingElseHere();
}
于 2009-10-19T16:02:05.603 に答える
4

初め:

これらのオブジェクトは、必要に応じて、コンストラクターではなくメンバーとして挿入できます。そうすれば、使用法が変わったときにコンストラクターを変更する必要がなく、静的を使用する必要もありません。

2番:

ある種のビルダーまたは工場を渡します。

三番:

どのクラスも、それ自体が必要とする依存関係のみを持つ必要があります。サブクラスには、独自の特定の依存関係を注入する必要があります。

于 2008-10-13T19:35:57.827 に答える
2

初め:

「興味のない」依存関係 (ILog、ICache、IApplicationSettings など) を保持するコンテナーを作成し、コンストラクター インジェクションを使用してそれを注入し、コンストラクターの内部で、container.Resolve( ) ? 私がそれを望んでいるかどうかはわかりませんが、まあ、それは可能性です。

または、新しい IServiceLocator 共通インターフェイス ( http://blogs.msdn.com/gblock/archive/2008/10/02/iservicelocator-a-step-toward-ioc-container-service-locator-detente ) を使用することもできます。 .aspx ) 依存関係を注入する代わりに?

2番:

オプション/オンデマンドの依存関係にセッター注入を使用できますか? 私は工場に注入し、そこからオンデマンドで新しいものを作るつもりだと思います.

于 2008-10-12T12:13:31.310 に答える
0

最初の質問に部分的に答えるために、構造マップとセッター注入を使用してオブジェクトのパブリック プロパティを自動設定する方法を示す、Jeremy Miller によるブログ投稿を見つけました。彼は例として ILogger を使用します。

var container = new Container(r =>
{
    r.FillAllPropertiesOfType<ILogger>().TheDefault.Is
        .ConstructedBy(context => new Logger(context.ParentType));
});

これは、ILogger プロパティを持つすべてのクラスを意味します。

public class ClassWithLogger
{
    public ILogger Logger { get; set; }
}

public class ClassWithLogger2
{
    public ILogger Logger { get; set; }
}

構築時に Logger プロパティが自動的に設定されます。

container.GetInstance<ClassWithLogger>();
于 2008-10-14T08:41:00.083 に答える