【问题标题】:Using IoC advanced features with own abstraction使用具有自己抽象的 IoC 高级功能
【发布时间】:2011-08-29 09:22:27
【问题描述】:

我阅读了许多关于在开发中使用 IoC 容器的有趣文章。在许多情况下,作者建议我们编写自己的简单包装器,该包装器隐藏读取容器的接口,如 Ninject、Castle.Windsor、Unity 等。

wrapper 的实现如下所示(对于 Ninject):

/// <summary>
/// Contains links for service contract and realization.
/// </summary>
public static class IoC
{
    static readonly IKernel kernel;
    static IoC()
    {
        kernel = new StandardKernel();
    }

    public static void RegisterService<T>(Type service)
    {
        kernel.Bind<T>().To(service);
    }

    public static T Resolve<T>()
    {
        return kernel.Get<T>();
    }
}

好的,但是我怎样才能使用这种模式的高级功能呢?例如,我喜欢使用 InRequestScope() 或 InSingletonScope() 之类的终身管理修饰符,并且许多容器都支持相同的修饰符。使用上面的模式,我可以扔掉 ninject 并使用15-lines implementation。区别在哪里?

我还查看了 CommonServiceLocator 试图找到丰富的包装器,但它与我的 IoC 类没有太大区别。 现在我认为我不应该自己制造自行车并直接使用功能齐全的容器。那你呢?

【问题讨论】:

  • 人们多久更换一次容器?真的那么普遍吗?无论如何,你的类不应该依赖于容器,所以替换它应该很容易,不管抽象与否,不是吗?
  • 通常情况下,没有理由将您的 DI 容器抽象出来,因为无论如何您都应该只在组合根中使用它。其他所有内容都称为service locator anti-pattern,应该避免使用。
  • @R。 Martinho Fernandes:FWIW 我上周在一个较大的实时代码库中切换了容器,但承认这不是我每周每周都做的事情:)

标签: .net inversion-of-control ninject


【解决方案1】:

您的应用程序代码应该完全忽略 DI 容器的存在,无论是哪个。相反,需要依赖的类应该通过 Constructor Injection 请求它们,如下所示:

public class Foo
{
    private readonly IBar bar;

    public Foo(IBar bar)
    {
        if (bar == null)
            throw new ArgumentNullException("bar");

        this.bar = bar;
    }

    public void SomeMethod()
    {
        // use this.bar from here...
    }
}

注意 Guard 子句和 readonly 关键字的组合如何保护类的不变量。从 Foo 类的任何方法中,this.Bar 保证可用。

所有 DI 容器都了解这种设计模式,因此您可以让您选择的容器构成来自 Composition Root 的整个应用程序图。从这里您可以所有使用容器的高级功能,而不必担心代码库的其余部分会与容器耦合。

【讨论】:

  • 所以,根据这个我应该使用一个包装器来解析。所有的绑定代码(包括 IoC depandant 部分)都应该被授权到根目录并且可以进行修改。对吗?
  • 您也不需要包装器来解析,因为您只需要在 Composition Root 中解析:blog.ploeh.dk/2010/09/29/TheRegisterResolveReleasePattern.aspx
【解决方案2】:

做了一些死灵术,但我认为其他答案错过了你的观点 - 部分。 您一直在谈论“高级功能”,例如范围界定。虽然实际实现没有引用容器,但它仍然在一定程度上依赖于这些特性。因此,我同意即使容器仅用于组合根目录,您的实现也可能不会 100% 与容器无关。如果是这样,那将意味着您在某些方面重新发明了轮子(例如请求范围...)

现在,如果您只需要像 SingletonRequest 这样的“标准”作用域 - 许多容器都支持它们 - 使用抽象(自定义)可能是值得的,这样您就可以轻松换出容器(例如,您可能想对它们进行基准测试,...)。

当您开始使用更高级的功能时,例如自定义范围、Ninject .InCallScope().InNamedScope(),更换容器的工作量越来越大。所以“抽象”几乎没有什么用处,因为“抽象”是容器特有的。

现在您应该彻底调查“锁定”到特定 DI 容器是否值得高级功能的好处。

您使用的容器功能越专业,切换容器的工作量就越多,可能很快就会变得不经济。它限制了你未来的选择。如果没有更新..你被卡住了。如果它对于不断增长的用户群来说太慢了......你就会陷入困境。

您必须平衡独立性、可测试性、可维护性、简单性(......也许还有更多标准)。

我不认为有人可以在不知道您的具体情况的情况下给您一个有效的答案。此外,这方面的经验往往取决于自己的经验。当一个人一直在开发框架时,一个人可能会说“永远不会”。如果您一直在开发中等规模的最终用户应用程序,您可能会说,这是值得的。

就我个人而言,我一直在使用诸如命名范围、调用范围、自定义范围、ninject.extensions.Factory(甚至更强大的自定义版本)、拦截、上下文保存、上下文操作、条件绑定等高级功能。 .. 很多。在整整 4 年的开发过程中(8 个开发人员),没有人对此感到遗憾。只是说一个人可以对此感到满意。但我仍然建议每个人都非常小心这个决定。我当然不提倡。

最后一件事。我所知道的最可怕的项目是一个,它试图把所有事情都做到完美,多次推迟发布,最终以大量花费而没有一毛钱收入。创建一个糟糕的软件,赚钱,然后不得不修复大量的技术债务是不好的。但与一分钱不赚相比,这是一个奢侈的问题。

【讨论】:

    【解决方案3】:

    我不建议抽象您的 IOC 框架。对于 95% 的应用程序,您没有充分的理由需要这样做。

    像这样暴露内核:

    public static class IoC
    {
         public IKernel Kernel { get { return _kernel; } }
    
         ... etc ...
    }
    

    如果您迫切希望抽象出您的 IOC 框架,那么当您需要时,我仍然可以通过某种方式直接访问真正的框架。例如,您可以将抽象内核转换为实际内核。当然,只有在您想利用抽象不提供的特定功能时,您才会偶尔这样做。

    关于您从 NInject 发布的示例需要注意的一点 - 我想说,该静态 IOC 类的目的是提供对内核单例实例的访问,而不是对应用程序隐藏 NInject。

    【讨论】:

    • 我不明白。为什么内核不能只是应用程序启动方法中的局部变量?为什么你需要完全暴露它?这不是有点像故意失去它给你的一些好处吗?
    • 在理想的世界里,是的,不接触内核会很棒。但是,如果您在任何类型的基于大型代码或任何类型的遗留平台(如 ASP.NET Web 窗体)上工作,那么您就不能一直依赖于构造函数注入。您需要能够执行 Kernel.Inject(this)、Kernel.Get 等操作。而且您经常需要访问内核来编写测试。
    猜你喜欢
    • 2013-04-11
    • 2016-06-08
    • 2016-05-13
    • 2017-04-04
    • 2023-03-10
    • 2021-05-23
    • 1970-01-01
    • 2011-05-30
    • 1970-01-01
    相关资源
    最近更新 更多