如何连接IoC容器以将值传递给工厂方法来解决?

时间:2014-05-22 18:59:47

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

背景/目标

  • 我们有几个"客户网站"在我们的网络应用程序上,用户可以在
  • 之间切换
  • 我们根据接收客户端站点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容器的新手,虽然我正在努力学习文献,但我觉得它可能比我制作它更容易,所以我要问这里。

1 个答案:

答案 0 :(得分:2)

最简单的方法可能是使用抽象工厂。大多数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签名。

  • 这也意味着您无法可靠地使IDependentObjects成为整个DI流程的一部分(即,他们自己拥有您希望框架解析的依赖图),因为强迫ctor签名。

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

希望有所帮助。