【问题标题】:Simple Dependency Resolver简单的依赖解析器
【发布时间】:2013-03-20 21:44:56
【问题描述】:

如何在不使用任何内置或库(如 Autofac、Ninject 等)的情况下创建简单的 Dependency Resolver。

这是我的面试问题。

我写了这个简单的代码,他们说它看起来不太好。这就像非常硬编码的想法。

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);

  }
}

我做了哪些更好的代码并为构造函数参数提供其他对象实例?

【问题讨论】:

  • 大部分 IoC 容器都是开源的,例如SimpleInjectorAutofac,让您可以窥探灵感。警告词 - 实施不是微不足道的。
  • 在旁注中,这是一个令人难以置信的智障面试问题。
  • 这个问题似乎是题外话,因为它基本上是要求进行代码审查。它属于“代码审查”。
  • 我投票结束这个问题,因为它要求批评代码

标签: c# design-patterns dependency-injection


【解决方案1】:

DI Containers 是复杂的库。建造它们需要数年时间并维护它们数十年。但是为了演示它们的工作原理,您可以用几行代码编写一个简单的实现。

在其核心,DI 容器通常会包装一个以System.Type 作为其键的字典,其值将是一些允许您创建该类型的新实例的对象。当您编写一个简单的实现时,System.Func&lt;object&gt; 就可以了。这是一个包含多个Register 方法的示例,包括通用和非通用GetInstance 方法并允许Auto-Wiring

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

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

    public void Register<TService>(Func<TService> factory) =>
        regs.Add(typeof(TService), () => factory());

    public void RegisterInstance<TService>(TService instance) =>
        regs.Add(typeof(TService), () => instance);

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

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

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

您可以按如下方式使用它:

var container = new Container();

container.RegisterInstance<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(即手动连接对象图)相比没有优势。你失去了编译时支持,没有得到任何回报。

当您的应用程序较小时,您应该从 Pure DI 开始,一旦您的应用程序和您的 DI 配置增长到维护您 Composition Root 变得很麻烦,您可以考虑切换到已建立的 DI 库之一。

与已建立的库相比,这个简单的实现缺少一些功能:

  • 自动注册:通过在单行中注册一组类型来应用约定优于配置的能力,而不必手动注册每种类型。
  • 拦截:为一系列类型应用装饰器或拦截器的能力
  • 泛型:将开放泛型抽象映射到开放泛型实现
  • 集成:将库与常用应用平台(如 ASP.NET MVC、Web API、.NET Core 等)结合使用
  • 生命周期管理:能够注册具有自定义生活方式的类型(例如 Scoped 或 Per Request)。
  • 错误处理:检测错误配置,例如循环依赖。这种简单的实现会引发堆栈溢出异常。
  • 验证:用于验证配置正确性(以补偿编译时支持的损失)和诊断常见配置错误的功能或工具。
  • 性能:使用这种简单的实现构建大型对象图会很慢(例如,由于产生的垃圾量很大,会导致大量 GC 压力)。

这些特性和能力允许您在使用 DI 容器时保持 DI 配置的可维护性。

【讨论】:

  • 这里 container.GetInstance(typeof(HomeController)) 返回 Object 类型,是否可以 TypeCast 并从 GetInstance() 方法本身返回 HomeController 类型,我在此代码中尝试了 stackoverflow.com/questions/36750123/…,但得到一些语法错误
  • 亲爱的 Steven,您将如何修改您的代码以创建具有标准服务定位器签名的方法...即var instance = _container.GetInstance()?
  • 你没有。您要么使用 Pure DI,要么使用通用 DI 库。
  • @Steven 你能详细说明为什么你不能吗?
  • 我的回答中的警告解释了这一点。
【解决方案2】:

已经有几年的历史了,但是 Ayende 曾经写过一篇关于这个的博文:
Building an IoC container in 15 lines of code

但这只是最简单的可能实现。
Ayende 本人在他的next post 中表示,现有的 IoC 容器可以做的不仅仅是返回类实例,而这正是它变得复杂的地方。
正如“相信我——我是一名医生”在他的评论中已经说过的那样:实现一个完整 IoC 容器绝非易事。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多