web-dev-qa-db-ja.com

単純な依存関係リゾルバー

Autofac、Ninjectなどの組み込みまたはライブラリを使用せずに、単純な依存関係リゾルバーをどのように作成しますか?.

これは私のインタビューの質問でした。

私はこの簡単なコードを書きました、そして彼らはそれが良く見えないと言いました。とてもハードにコード化されたアイデアのようです。

public interface IRepository { }
interface IDataProvider
{
    List<string> GetData();
}
public class SQLDataProvider : IDataProvider
{
    private readonly IRepository _repository { get; set; }
    public SQLDataProvider(IRepository repository)
    {
        _repository = repository;
    }
    public List<string> GetData()
    {
        return new List<string> { "" };
    }
}
public class MockDataProvider : IDataProvider
{
    public List<string> GetData()
    {
        return new List<string> { "" };
    }
}
class Program
{
 static void Main(string[] args)
 {
    string targetClass = "SQLDataProvider";
    //Here i need to supply IRepository instance too 
   IDataProvider dataProvider = 
   (IDataProvider)Activator.CreateInstance(typeof(IDataProvider), targetClass);

  }
}

どのような優れたコードを実行して、コンストラクターパラメーターの他のオブジェクトインスタンスを提供しますか?

30
Billa

数行のコードでコンテナーを作成できます。その中核は、通常、System.Typeをキーおよび値として使用すると、そのタイプの新しいインスタンスを作成できるオブジェクトになります。単純な実装を書くときSystem.Func<object>します。次に、いくつかのRegisterメソッドを含む単純な実装を示します。ジェネリックと非ジェネリックの両方のGetInstanceメソッドで、自動配線が可能です。

public class Container 
{
    Dictionary<Type, Func<object>> registrations = new Dictionary<Type, Func<object>>();

    public void Register<TService, TImpl>() where TImpl : TService {
        this.registrations.Add(typeof(TService), () => this.GetInstance(typeof(TImpl)));
    }

    public void Register<TService>(Func<TService> instanceCreator) {
        this.registrations.Add(typeof(TService), () => instanceCreator());
    }

    public void RegisterSingleton<TService>(TService instance) {
        this.registrations.Add(typeof(TService), () => instance);
    }

    public void RegisterSingleton<TService>(Func<TService> instanceCreator) {
        var lazy = new Lazy<TService>(instanceCreator);
        this.Register<TService>(() => lazy.Value);
    }

    public object GetInstance(Type serviceType) {
        Func<object> creator;
        if (this.registrations.TryGetValue(serviceType, out creator)) return creator();
        else if (!serviceType.IsAbstract) return this.CreateInstance(serviceType);
        else throw new InvalidOperationException("No registration for " + serviceType);
    }

    private object CreateInstance(Type implementationType) {
        var ctor = implementationType.GetConstructors().Single();
        var parameterTypes = ctor.GetParameters().Select(p => p.ParameterType);
        var dependencies = parameterTypes.Select(t => this.GetInstance(t)).ToArray();
        return Activator.CreateInstance(implementationType, dependencies);
    }
}

次のように使用できます。

var container = new Container();

container.RegisterSingleton<ILogger>(new FileLogger("c:\\logs\\log.txt"));

// SqlUserRepository depends on ILogger
container.Register<IUserRepository, SqlUserRepository>();

// HomeController depends on IUserRepository
// Concrete instances don't need to be resolved
container.GetInstance(typeof(HomeController));

[〜#〜]警告[〜#〜]

このような実装を実際に使用してはいけないことに注意してください。 DIライブラリが提供する多くの重要な機能が不足していますが、 Pure DI (つまり、オブジェクトグラフを手動で配線する)を使用することに勝る利点はありません。何も戻さずに、コンパイル時のサポートを失います。

アプリケーションが小さい場合は、純粋なDIから開始する必要があります。アプリケーションとDI構成が大きくなり、維持が困難になると、 構成ルート が確立されたDIライブラリのいずれかに切り替えることを検討できます。 。

確立されたライブラリと比較して、この素朴な実装には欠けている機能のいくつかを次に示します。

  • 一括登録(一式で型のセットを登録する)
  • さまざまなタイプのデコレーターまたはインターセプターの適用
  • オープンなジェネリック抽象化をオープンなジェネリック実装にマッピングする
  • 一般的なアプリケーションプラットフォーム(ASP.NET MVC、Web APIなど)との統合
  • タイプをカスタムライフスタイルに登録する。
  • まともなエラーレポート(たとえば、スタックオーバーフロー例外をスローする代わりに)
  • 構成の正しさを検証し(コンパイル時のサポートの喪失を補うため)、一般的な構成の誤りを診断するためのツール。
  • 良い成果。

これらの機能により、DI構成を保守可能な状態に保つことができます。

31
Steven

すでに数年前ですが、Ayendeはかつてこれについてブログ投稿を書いています。
15行のコードでIoCコンテナーを構築する

しかし、これは可能な限り非常に単純な実装です。
アインデ自身が 次の投稿 で述べたように、既存のIoCコンテナはクラスインスタンスを返すだけではなく、もっと多くのことを行うことができます-ここが複雑になります。
「Trust me-I'm a Doctor」のコメントですでに述べたように、completeIoCコンテナの実装は簡単ではありません。

4