【问题标题】:How do I wire an IoC Container to pass a value to a factory method to resolve?如何连接 IoC 容器以将值传递给工厂方法以进行解析?
【发布时间】:2014-05-22 18:59:47
【问题描述】:

背景/目标

  • 我们的网络应用中有多个“客户端站点”,用户可以在它们之间切换
  • 我们根据工厂对对象进行大量连接,这些工厂接受客户端站点 ID 并创建实例
  • 我想将这些依赖项注入到类中
  • 我还想确保我可以将自己的实现传递给构造函数以进行单元测试。
  • 我们最初选择使用 StructureMap 3.x 来执行此操作,但如果它们可以帮助我们优雅地解决这种情况,我们愿意接受替代方案。

问题

  • 在我需要基于仅在运行时获得的客户端站点 ID 的不同依赖项的情况下,设置 IoC 容器的适当方法是什么,以及从其中请求对象的适当方法是什么?为了让它尽可能无痛?
  • 我是否在考虑这种错误并无意中创建了某种反模式?

示例代码

通常我们会做如下的事情:

public class MyService
{   DependentObject _dependentObject;

    public MyService(int clientSiteID)
    {
       // ...
       _dependentObject = new dependentObjectFactory(clientSiteID).GetDependentObject();

    }

    public void DoAThing()
    {    
         //...
         _dependentObject.DoSomething(); 
    }

}

我想做的事:

public class MyService
{   DependentObject _dependentObject;

    public MyService(int clientSiteID)
    {
       // ...
       _dependentObject = MyTypeResolver.GetWIthClientContext<IDependentObject>(clientSiteID);
    }

    public MyService(int clientSiteID, IDependentObject dependentObject)
    {
       // ...
       _dependentObject = dependentObject;

    }

    public void DoAThing()
    {    
         //...
         _dependentObject.DoSomething(); 
    }

}

我将设置 IoC 容器,使我可以使用我的MyTypeResolver 传入clientSiteID,并让容器调用我的DependentObjectFactory 并返回正确的对象结果。

我是 IoC 容器的新手,虽然我正在尝试阅读文献,但我觉得这可能比我制作它更容易,所以我在这里问。

【问题讨论】:

    标签: c# dependency-injection inversion-of-control


    【解决方案1】:

    可能最简单的方法是使用抽象工厂。大多数 IOC 框架都可以为您自动创建它们,但这里是您可以手动完成的方法(我总是喜欢先手动完成,所以我知道它可以工作,然后您可以查看该框架如何帮助您实现自动化)

    现在要提一件事 - 我建议对最终解决方案的工作方式进行轻微调整,但一旦我展示了它目前的工作方式,我将继续讨论它。下面的例子假设Ninject,请原谅任何错别字等。

    首先为你的依赖创建一个接口

    public interface IDependentObject
    {
        void DoSomething();
    }
    

    然后为IDependentObject的每个具体实现声明空标记接口

    public interface INormalDependentObject:IDependentObject{};
    public interface ISpecialDependentObject:IDependentObject{}
    

    并实施它们:

    public class NormalDependentObject:INormalDependentObject
    {
        readonly int _clientID;
        public DependentObject(int clientID)
        {
           _clientID=clientID;
        }
        public void DoSomething(){//do something}
    }
    
    public class DependentObject:ISpecialDependentObject
    {
        readonly int _clientID;
        public DependentObject(int clientID)
        {
           _clientID=clientID;
        }
        public void DoSomething(){//do something really special}
    }
    

    当然,正如您所提到的,您可能有更多 IDependentObject 的实现。

    可能有一种更优雅的方式可以让您的 IOC 框架在运行时解析而无需声明标记接口;但现在我发现使用它们很有用,因为它使绑定声明易于阅读:)

    接下来,声明IDependentObjectFactory的接口和实现:

    public interface IDependentObjectFactory
    {
       IDependentObject GetDependenObject(int clientID);
    }
    
    public class DependentObjectFactory: IDependentObjectFactory
    {
        readonly _kernel kernel;
        public DependentObjectFactory(IKernel kernel)
        {
            _kernel=kernel;
        }
    
        public IDependentObject GetDependenObject(int clientID)
        {
              //use whatever logic here to decide what specific IDependentObject you need to use.
              if (clientID==100)
              {
                  return _kernel.Get<ISpecialDependantObject>(
                         new ConstructorArgument("clientID", clientID));
              }
              else
              {
                 return _kernel.Get<INormalDependentObject>(
                         new ConstructorArgument("clientID", clientID));
              }
        }    
    }
    

    将这些连接到您的合成根中:

    _kernel.Bind<INormalDependentObject>().To<NormalDependentObject>();
    _kernel.Bind<ISpecialDependentObject>().To<SpecialDependentObject>();
    _kernel.Bind<IDependentObjectFactory>().To<DependentObjectFactory>();
    

    最后将你的工厂注入到服务类中:

    public class MyService
    {   
        IDependentObject _dependentObject;
        readonly IDependentObjectFactory _factory;    
    
        //in general, when using DI, you should only have a single constructor on your injectable classes. Otherwise, you are at the mercy of the framework as to which signature it will pick if there is ever any ambiguity; most all of the common frameworks will make different decisions!
        public MyService(IDependentObjectFactory factory)
        {
           _factory=factory;
        }
    
        public void DoAThing(int clientID)
        {    
             var dependent  _factory.GetDependentObject(clientID);
             dependent.DoSomething();
        }
    }
    

    建议的更改

    • 与上述结构的一个直接变化是我将clientID 排除在服务构造函数之外,并将其移至DoAThing 的方法参数;这是因为服务本身是无状态的对我来说更有意义;当然,根据您的情况,您可能不想这样做。

    我提到我有一个轻微的调整建议,就是这样;上面的解决方案取决于(没有双关语!)IDependentObject 的实现具有具有此签名的构造函数:

    public SomeDependency(int clientID)
    
    • 如果他们没有该签名,那么工厂将无法工作;就我个人而言,我不希望我的 DI 必须了解有关构造函数参数的任何信息,因为它使您摆脱了纯粹处理接口并迫使您在具体类上实现特定的 ctor 签名。

    • 这也意味着由于强制 ctor 签名,您无法可靠地使您的 IDependentObjects 成为整个 DI 过程的一部分(即它们本身具有您希望框架解决的依赖关系图)。

    • 出于这个原因,我建议将IDependentObject.DoSomething() 本身更改为DoSomething(int clientID),这样您就可以省略工厂代码的new ConstructorArgument 部分;这意味着您的 IDependentObject 现在都可以具有完全不同的 ctor 签名,这意味着如果需要它们可以具有不同的依赖关系。当然这只是我的看法,你会知道在你的具体情况下什么最有效。

    希望对您有所帮助。

    【讨论】:

    • 感谢您的出色回答。我正在审查它并研究我们将如何管理它,但我很确定这是正确的答案,并且我肯定会尽快接受这个答案。这是彻底的、有教育意义的、组织良好的。如果可以的话,我会多次投票。
    • 不用担心,请务必回帖,让我们知道这是否适合您 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-16
    相关资源
    最近更新 更多