如何连接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框架都可以自动为您创建它们,但这里是如何手动创建它们的方法(我总是喜欢先手动创建,这样我就知道它是有效的,然后您可以查看框架如何帮助您自动创建它)
现在有一件事要提一下——我建议稍微调整一下最终解决方案的工作方式,但在展示了它当前的工作方式之后,我将深入讨论这一点。下面的示例假设为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签名,这意味着如果需要,它们可以具有不同的依赖关系。当然,这只是我的意见,你会知道什么在你的具体情况下最有效。
希望对你有帮助。