如何连接IoC容器以将值传递给要解析的工厂方法?

本文关键字:方法 工厂 值传 连接 何连接 IoC | 更新日期: 2023-09-27 18:04:35

背景/目标

  • 我们的web应用程序中有几个"客户端站点",用户可以在
  • 之间切换。
  • 我们做了很多基于工厂的对象连接,这些工厂接受客户端站点ID并创建一个实例
  • 我想把这些依赖项注入到类中,而不是
  • 我还想确保我可以将自己的实现传递给构造函数以进行单元测试。
  • 我们最初选择使用StructureMap 3。但是如果他们能帮助我们优雅地解决这个场景,我们对其他选择持开放态度。

  • 在我需要基于客户端站点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容器的新手,当我试着阅读文献时,我觉得这可能比我做的更容易,所以我在这里问。

如何连接IoC容器以将值传递给要解析的工厂方法?

可能最简单的方法是使用抽象工厂。大多数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必须了解构造函数参数,因为它使您脱离了纯粹处理接口的工作,并迫使您在具体类上实现特定的函数签名。

  • 这也意味着你不能可靠地使你的IDependentObjects成为整个DI过程的一部分(即,它们本身有依赖关系图,你希望框架解析),因为强制的tor签名

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

希望对你有帮助。