【问题标题】:how do IoC this?IoC这个怎么做?
【发布时间】:2011-07-01 12:19:35
【问题描述】:

我希望了解 Ioc/Di 如何简化我经常使用的以下类的布线。

考虑一个库,它具有实体的抽象概念和数据访问对象的接口:

public abstract class EntityWithTypedId<TId> : IEntityWithTypedId<TId>{...}

public interface IDao<T, TId> where T : IEntityWithTypedId<TId>

对于 dao,我有一个 NHibernate 实现以及一个我认为对测试有用的假 dao:

// has an NHib implementation
public class Dao<T, TId> : IDao<T, TId> where T : EntityWithTypedId<TId> {...}

public class DaoFakeBase<T> : IDao<T, int>, IDisposable where T : IEntityWithTypedId<int> {...}

我目前执行以下操作来为给定项目定义 Entity 和 Dao 类型:

/// <summary>
/// <see cref="IEntityWithTypedId{IdT}"/> with an int for an id
/// </summary>
[Serializable]
public abstract class Entity : EntityWithTypedId<int>
{
}

public class Dao<T> : Dao<T, int> where T : Entity
{
    protected Dao(ISessionFactory sessionFactory) : base(sessionFactory) { }

}

我可以改用 DI 工具来定义实体吗?如果是这样,有人可以向我展示如何执行此操作的代码示例吗?

您能否也列出我如何告诉我的测试程序集使用 DaoFakes 和生产使用 NHib.Dao

我一直在关注 Windsor,主要是因为 NHibernate contrib 项目使用它,但我也对 MEF、AutoFac 和 Ninject 感兴趣,按此顺序排列。我意识到 MEF 不是 Windsor 意义上的 IoC 容器。从我在 Windsor 中看到的情况来看,我会使用 Installer 类,可能是 EntityInstaller 和 DaoInstaller,尽管我也可能在这里遗漏了一个 FActory 类型的对象。

干杯,
浆果

更新@KeithS

你是说要改变类似的东西:

 class MyViewModel(IDao<MyClass, int> dao) {...}

变成类似

class MyViewModel(Func<IDao<MyClass, int>, obj> getDaoFunc) {
    _dao = getDaoFunc(this);
}   

【问题讨论】:

  • 您没有列出 Prism/Unity,所以我不会将其添加为正式答案...但是归结为简单地注册具有具体类型的接口。如果您愿意,可以将定义包装在 #if debug 中; IUnityContainer.RegisterType();因此在运行时 IFileManager 的所有解析都将解析为 FileManager 类型。
  • @Aaron 这不是违背了使用接口的目的吗?
  • @Brian No...这是整个应用程序中唯一引用具体类型的行。其他任何地方都在不了解实现细节的情况下引用该接口。在每个模块中(由 Prism 调用)都有一个 init 方法,在该方法中进行注册……如上所示,这是一个单行。
  • @Aaron 谢谢;我不熟悉 Prism/Unity 因此我的问题。
  • 通过委托解析类型实际上比简单地将类型的实例注入构造函数有点“混乱”。无论哪种方式,你都可以做到。实例注入总体上更简单。委托注入允许对您不需要的对象进行延迟实例化,或者甚至根本不需要(如果您的对象可以在没有依赖关系的情况下执行一些常见任务)。方法注入可以减少初始加载时间,但会在您真正需要它时弥补该时间(但可能不会实例化一堆其他东西)。

标签: c# dependency-injection castle-windsor ioc-container mef


【解决方案1】:

在你的例子中......

class MyViewModel(IDao<MyClass, int> dao) {...}

...IDao 将在运行时根据容器中的先前注册得到解决。 Prism/Unity 实现的语法如下...

IUnityContainer.RegisterType<IDao..., DaoFakeBase...>();

RegisterType 发生在UnityBootstrapper 类中定义的给定模块的 IModule.Initialize() 中。

protected override IModuleCatalog GetModuleCatalog()
{
    ModuleCatalog catalog = new ModuleCatalog();
    catalog.AddModule(typeof(project.Init));
    return catalog;
}

您还可以根据生命周期管理器注册给定类型;表现得像一个单身人士......

IUnityContainer.RegisterType<IShellController, ShellController>(new ContainerControlledLifetimeManager());

...IShellController 解析的实例将在IUnityContainer 的整个生命周期内保持相同的返回实例。

更新:

使用您的代码,注册将如下所示...

    public interface IDao<T, TId> where T : IEntityWithTypedId<TId>
    { }

    public class Dao<T, TId> : IDao<T, TId> where T : EntityWithTypedId<TId> 
    { }

    public class TId
    { }

    public abstract class EntityWithTypedId<TId> : IEntityWithTypedId<TId>
    { }

    public interface IEntityWithTypedId<TId>
    { }

    IUnityContainer.RegisterType<IEntityWithTypedId<TId>, EntityWithTypedId<TId>>();
    IUnityContainer.RegisterType<IDao<IEntityWithTypedId<TId>, TId>, Dao<IEntityWithTypedId<TId>, TId>>();

    IDao<IEntityWithTypedId<TId>, TId> dao = IUnityContainer.Resolve<IDao<IEntityWithTypedId<TId>, TId>>();

【讨论】:

  • IEntityWithTypedId 中的 TId 怎么样,您是否也尝试注册它?另外,IDao 的泛型类型看起来如何?
  • @Berryl 根据您的要求添加了代码...是的,如果 TId 的范围超出单个类,我会注册 TId。
  • 对不起,让你这么辛苦! TId 通常是 int、long 或 GUID(即结构),因此不确定它的类定义。 IUnityContainer.RegisterType?您最接近回答我关于如何使用 IoC 的问题。谢谢
  • @Berryl 我会将它包装在某种界面中;也许你在哪里暴露 IId.ID 或类似的东西。 IUnityContainer.RegisterType
【解决方案2】:

我不会使用 IoC 来注册 DAO 及其类型之间的关系(这基本上就是您要做的)。这将导致您将 IoC 容器用作“服务定位器”,这是一种已知的反模式,您将 IoC 容器传递到对象中,这些对象将使用它来获取所需的 DAO。

我认为从消费角度简化这一点的最佳方法是定义策略模式,使用工厂类或方法:

public Dao<T, TId> GetDaoFor<T, TId>(T objectInstance) where T:EntityWithTypedId<TId>
{
    //Here, you could use a Dictionary, Linq with some reflection, etc.
}

这个方法可以作为委托注入到依赖于 DAO 的类中。不同之处在于,需要 DAO 的类依赖于可以将其提供给它们的方法,该方法可以由 IoC 容器提供;它们不依赖于容器本身(这是“服务定位器”模式固有的主要邪恶来源)。如果您重新编写如何获得这些 DAO,这将减少您必须更改的内容的数量。

编辑:有点离题,但我打开了门:

通常要避免使用服务定位模式,因为它会导致代码依赖于服务定位器。例如,以下在 IoC 已在子级别公开的代码中很常见:

private IDependency _dependency;
public IDependency MyDependency
{
   get {
         _dependency = _dependency ?? IoC.Resolve<IDependency>();
         return _dependency;
       }
}

虽然这似乎是一个不错的模式(依赖项被延迟初始化,使用代码不需要知道子项的依赖项,并且您总是*获得引用),但此代码始终需要 IoC 单例存在。您可以更改其背后的 IoC 框架,您可以完全移除第三方工具并推出自己的工具,但此类始终需要静态调用 Resolve&lt;IDependency&gt;() 的东西。

你也不会总是得到参考;只有在您向 IoC 正确注册 IDependency 时,您才会获得参考。这又产生了两个弱点; 1)如果不打开它,您不知道该类将需要什么,以及 2)如果/当调用失败时,它将在依赖类的内部工作的深处失败。如果您开发一个新类,并将其插入 IoC,它可能会通过集成,甚至可以在生产中工作一段时间,直到您在代码中非常奇怪的地方开始出现奇怪的“对象引用设置为 null”错误,即,相信我,调试的噩梦。

最后,单元测试服务定位器模式代码更困难,原因很简单,您必须模拟服务定位器以及服务定位器提供的依赖项。您可以让生产服务定位器继续使用,只需将模拟类注册为依赖项,但这不是单元测试;测试依赖于,因此在某种程度上测试,类及其服务定位器的集成按预期工作。这是一个集成测试。

相比之下,依赖注入模式使您摆脱了对如何解决依赖关系的任何依赖。唯一的要求(在构造函数注入中)是它们在创建类时就在附近。这有几个优点:

  • 如果不使用 IoC 框架,您必须知道类需要什么来实例化它。
  • 如果使用 IoC 框架,则在尝试实例化依赖类时会出现运行时错误,而不是稍后实际解析对象时。
  • 测试依赖类时,您可以更轻松地模拟依赖项,因为不必通过服务定位器输入依赖项。
  • 在大多数 IoC 框架中,您仍然可以通过提供工厂方法而不是对构造函数的实际依赖项来延迟初始化依赖项。然后,上述模式调用该委托,该委托可以来自任何地方,而不是由整个代码库中的一个且唯一一个构造满足的静态命名方法。

【讨论】:

  • 依赖容器接口有什么顾虑?是否需要更换容器实现?你总是可以用你自己的抽象出容器接口。
  • @KeithS。我认为我喜欢你所说的,但我还没有考虑清楚(你总是看到容器注入 IDao 的例子)。你是说消费者会在我的问题结束时看起来像编辑?
  • @Aaron。 Keith 并不在寻求容器独立性,而是希望避免将容器用作服务定位器。
  • @KiethS。服务定位器反模式是否意味着要求一些单例来解析类中的类型,而只通过注入该类型来解决?
  • @Berryl 我试图了解 Keith 对将容器用作“服务定位器”的担忧......
猜你喜欢
  • 1970-01-01
  • 2012-07-09
  • 1970-01-01
  • 2013-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-05
  • 1970-01-01
相关资源
最近更新 更多