代码之家  ›  专栏  ›  技术社区  ›  andreialecu

使用依赖注入注入注入依赖注入器

  •  8
  • andreialecu  · 技术社区  · 15 年前

    对于依赖注入来说,这是一个很新的概念,我正试图弄清楚这是否是一个反模式。

    假设我有三个组件:

    Foo.Shared - this has all the interfaces
    Foo.Users - references Foo.Shared
    Foo.Payment - references Foo.Shared
    

    用户需要一个在foo.payment和foo.payment中构建的对象。payment也需要foo.users提供的东西。这会创建某种循环依赖关系。

    我在foo.shared中定义了一个接口,它代理我正在使用的依赖注入框架(在本例中是ninject)。

    public interface IDependencyResolver
    {
        T Get<T>();
    }
    

    在容器应用程序中,我有这个接口的实现:

    public class DependencyResolver:IDependencyResolver
    {
        private readonly IKernel _kernel;
    
        public DependencyResolver(IKernel kernel)
        {
            _kernel = kernel;
        }
    
        public T Get<T>()
        {
            return _kernel.Get<T>();
        }
    }
    

    配置如下:

    public class MyModule:StandardModule
    {
        public override void Load()
        {
            Bind<IDependencyResolver>().To<DependencyResolver>().WithArgument("kernel", Kernel);
            Bind<Foo.Shared.ISomeType>().To<Foo.Payment.SomeType>(); // <- binding to different assembly
            ...
        }
    }
    

    这允许我实例化 Foo.Payment.SomeType 从foo.用户内部无需直接参考:

    public class UserAccounts:IUserAccounts
    {
        private ISomeType _someType;
        public UserAccounts(IDependencyResolver dependencyResolver)
        {
            _someType = dependencyResolver.Get<ISomeType>(); // <- this essentially creates a new instance of Foo.Payment.SomeType
        }
    }
    

    这使得我们不清楚 UserAccounts 在这种情况下,我认为这不是一个好的实践。

    我还能怎么做到呢?

    有什么想法吗?

    3 回复  |  直到 15 年前
        1
  •  7
  •   Mark Seemann    15 年前

    尽管有些争议:是的,这是一种反模式。它被称为 服务定位器 虽然有些人认为它是一个合适的设计模式,但我认为它是一个反模式。

    这个问题是,例如,您的useraccounts类的用法 隐性的 而不是 明确的 . 虽然构造器声明它需要IDependencyResolver,但它没有声明应该包含什么。如果传递给它的IDependencyResolver无法解析isomeType,它将抛出。

    更糟糕的是,在以后的迭代中,您可能会尝试解决一些 其他类型 从用户帐户中。它将编译得很好,但如果/当类型无法解析时,它可能会在运行时抛出。

    别走那条路。

    根据给出的信息,不可能确切地告诉您应该如何用循环依赖性解决特定的问题,但我建议您重新考虑您的设计。在许多情况下,循环引用是 泄漏的抽象 因此,如果您对API进行一点改造,它可能会消失——通常令人惊讶的是,所需的更改非常小。

    一般来说,任何问题的解决方案都是添加另一个间接层。如果您确实需要两个库中的对象紧密协作,那么通常可以引入一个中间代理。

    • 在许多情况下, 发布/订阅 模型工作得很好。
    • 这个 调解人 模式可以提供一种替代的国际单项体育联合会通信 必须 走两条路。
    • 你也可以介绍一个 抽象工厂 要根据需要检索所需的实例,而不是要求立即将其连接起来。
        2
  •  2
  •   Richard Beier    15 年前

    我同意Foreverdebugging——消除循环依赖性会很好。看看您是否可以这样划分类:

    • foo.payment.dll:只处理付款的类,不处理用户的类
    • foo.users.dll:只处理用户而不处理付款的类
    • foo.userpayment.dll:处理付款和用户的类

    然后有一个程序集引用其他两个程序集,但没有依赖关系圈。

    如果程序集之间确实存在循环依赖关系,则不一定意味着类之间存在循环依赖关系。例如,假设您有这些依赖项:

    • foo.users.useraccounts依赖foo.shared.ipPaymentThistory,由foo.payment.paymentHistory实现。
    • 另一种支付类别foo.payment.paymentgateway依赖于foo.shared.iuseraccounts。iuseraccounts由foo.users.useraccounts实现。

    假设没有其他依赖项。

    这里有一个程序集循环,这些程序集在运行时在应用程序中相互依赖(尽管它们在编译时并不相互依赖,因为它们通过共享的DLL)。但在编译时或运行时,没有相互依赖的类循环。

    在这种情况下,您应该仍然能够正常使用IOC容器,而不需要添加额外的间接级别。在mymodule中,只需将每个接口绑定到适当的具体类型。使每个类接受其依赖项作为构造函数的参数。当顶级应用程序代码需要类的实例时,让它向IOC容器请求该类。让IOC容器担心查找类所依赖的所有内容。



    如果最终在类之间存在循环依赖关系,则可能需要在其中一个类上使用属性注入(即setter注入),而不是构造函数注入。我不使用ninject,但它支持属性注入- here is the documentation .

    通常IOC容器使用构造函数注入-它们将依赖项传递给依赖它们的类的构造函数。但当存在循环依赖时,这不起作用。如果类A和类B相互依赖,则需要将类A的实例传递给类B的构造函数。但是,为了创建A,需要将类B的实例传递给它的构造函数。这是鸡和蛋的问题。

    通过属性注入,您告诉IOC容器首先调用构造函数,然后在构造的对象上设置属性。通常,这用于可选的依赖项,如记录器。但您也可以使用它来打破两个相互需要的类之间的循环依赖关系。

    但这并不是很好,我绝对建议重构类以消除循环依赖性。

        3
  •  1
  •   Kye    15 年前

    这对我来说确实有点奇怪。是否可以将需要两个引用的逻辑分离到第三个程序集中,以打破依赖关系并避免风险?