问题描述
我目前正在学习Autofac的API,并且正在努力弄清我认为这是一个非常常见的用例.
我有一个类(对于这个简单的示例'MasterOfPuppets'),该类具有通过构造函数注入('NamedPuppet')接收到的依赖关系,该依赖关系需要使用(字符串名称)来构建值:
public class MasterOfPuppets : IMasterOfPuppets { IPuppet _puppet; public MasterOfPuppets(IPuppet puppet) { _puppet = puppet; } } public class NamedPuppet : IPuppet { string _name; public NamedPuppet(string name) { _name = name; } }
我用它们的接口注册了两个类,然后我想用将被注入到'NamedPuppet'实例中的字符串来解析IMasterOfPuppets.
我尝试通过以下方式进行操作:
IMasterOfPuppets master = bs.container.Resolve<IMasterOfPuppets>(new NamedParameter("name", "boby"));
这以运行时错误结尾,因此我想Autofac仅尝试将其注入到"MasterOfPuppets"中.
所以我的问题是,如何才能以最优雅的方式仅解析"IMasterOfPuppets"并将参数参数传递给它的依赖项?其他ioc容器是否有更好的解决方案?
Autofac不支持将参数传递给父对象/消费者对象并将这些参数滴入子对象.
通常,我会说要求消费者了解其依赖项接口背后的内容是错误的设计.让我解释一下:
在您的设计中,您有两个接口:IMasterOfPuppets和IPuppet.在示例中,您只有IPuppet-NamedPuppet的一种类型.请记住,即使拥有接口,也是要从实现中分离接口,您可能还需要在系统中添加它:
public class ConfigurablePuppet : IPuppet { private string _name; public ConfigurablePuppet(string name) { this._name = ConfigurationManager.AppSettings[name]; } }
有两件事要注意.
首先,您有一个 IPuppet 的不同实现,当与IMasterOfPuppets使用者一起使用时,该实现可以代替任何其他IPuppet. IMasterOfPuppets的实现永远都不应该知道IPuppet的实现已更改...并且应该进一步删除消耗IMasterOfPuppets的事物.
第二,示例NamedPuppet和新的ConfigurablePuppet都采用了具有相同名称的字符串参数,但它意味着与支持实现不同.因此,如果您使用的代码正在执行示例中显示的操作-传入旨在作为事物的 name 的参数-那么您可能会遇到接口设计问题.请参阅:李斯科夫替代原则.
要点是,考虑到IMasterOfPuppets 实现需要传入的IPuppet,它不关心如何构造IPuppet 首先,它是对接口和实现的分离,这意味着您也可以取消该接口,而只需传入NamedPuppet一直都是对象.
就传递参数而言, Autofac确实具有参数支持.
推荐的最常见的参数传递类型是注册期间因为那时您可以在容器级别进行设置,而您不使用服务位置(即通常认为是反模式).
如果您需要在解析期间传递参数,则 Autofac也支持 .但是,在解析过程中通过时,它更像是服务定位符,而不是那么好,因为它再次表明消费者知道它在消费什么.
如果您愿意,可以使用 lambda表达式注册希望连接参数来自已知来源(例如配置).
builder.Register(c => { var name = ConfigurationManager.AppSettings["name"]; return new NamedPuppet(name); }).As<IPuppet>();
您还可以使用使用者中的Func<T>隐式关系:
public class MasterOfPuppets : IMasterOfPuppets { IPuppet _puppet; public MasterOfPuppets(Func<string, IPuppet> puppetFactory) { _puppet = puppetFactory("name"); } }
这样做等同于在解析过程中使用类型为string的TypedParameter.但是,正如您所看到的,它来自IPuppet的直接使用者,而不是滴入所有分辨率的堆栈中的东西.
最后,您还可以使用 Autofac模块做一些有趣的事情以您在 log4net集成模块示例中看到的方式横切事物.使用这样的技术可以使您通过所有分辨率全局插入特定的参数,但是不一定提供在运行时传递参数的功能-您必须将参数的源放在模块内部./p>
要点是 Autofac支持参数,但不支持您要尝试的操作.我强烈建议您重新设计您的操作方式,这样您实际上就不需要做自己的事情了.正在执行操作,或者您可以通过上面提到的一种方法来解决它.
希望这能使您朝正确的方向前进.
I'm currently learning the API for Autofac, and I'm trying to get my head around what seems to me like a very common use case.
I have a class (for this simple example 'MasterOfPuppets') that has a dependency it receives via constructor injection ('NamedPuppet'), this dependency needs a value to be built with (string name):
public class MasterOfPuppets : IMasterOfPuppets { IPuppet _puppet; public MasterOfPuppets(IPuppet puppet) { _puppet = puppet; } } public class NamedPuppet : IPuppet { string _name; public NamedPuppet(string name) { _name = name; } }
I register both classes with their interfaces, and than I want to resolve IMasterOfPuppets, with a string that will be injected into the instance of 'NamedPuppet'.
I attempted to do it in the following way:
IMasterOfPuppets master = bs.container.Resolve<IMasterOfPuppets>(new NamedParameter("name", "boby"));
This ends with a runtime error, so I guess Autofac only attempts to inject it to the 'MasterOfPuppets'.
So my question is, how can I resolve 'IMasterOfPuppets' only and pass parameter arguments to it's dependency, in the most elegant fashion?Do other ioc containers have better solutions for it?
Autofac doesn't support passing parameters to a parent/consumer object and having those parameters trickle down into child objects.
Generally I'd say requiring the consumer to know about what's behind the interfaces of its dependencies is bad design. Let me explain:
From your design, you have two interfaces: IMasterOfPuppets and IPuppet. In the example, you only have one type of IPuppet - NamedPuppet. Keeping in mind that the point of even having the interface is to separate the interface from the implementation, you might also have this in your system:
public class ConfigurablePuppet : IPuppet { private string _name; public ConfigurablePuppet(string name) { this._name = ConfigurationManager.AppSettings[name]; } }
Two things to note there.
First, you have a different implementation of IPuppet that should work in place of any other IPuppet when used with the IMasterOfPuppets consumer. The IMasterOfPuppets implementation should never know that the implementation of IPuppet changed... and the thing consuming IMasterOfPuppets should be even further removed.
Second, both the example NamedPuppet and the new ConfigurablePuppet take a string parameter with the same name, but it means something different to the backing implementation. So if your consuming code is doing what you show in the example - passing in a parameter that's intended to be the name of the thing - then you probably have an interface design problem. See: Liskov substitution principle.
Point being, given that the IMasterOfPuppets implementation needs an IPuppet passed in, it shouldn't care how the IPuppet was constructed to begin with or what is actually backing the IPuppet. Once it knows, you're breaking the separation of interface and implementation, which means you may as well do away with the interface and just pass in NamedPuppet objects all the time.
As far as passing parameters, Autofac does have parameter support.
The recommended and most common type of parameter passing is during registration because at that time you can set things up at the container level and you're not using service location (which is generally considered an anti-pattern).
If you need to pass parameters during resolution Autofac also supports that. However, when passing during resolution, it's more service-locator-ish and not so great becausee, again, it implies the consumer knows about what it's consuming.
You can do some fancy things with lambda expression registrations if you want to wire up the parameter to come from a known source, like configuration.
builder.Register(c => { var name = ConfigurationManager.AppSettings["name"]; return new NamedPuppet(name); }).As<IPuppet>();
You can also do some fancy things using the Func<T> implicit relationship in the consumer:
public class MasterOfPuppets : IMasterOfPuppets { IPuppet _puppet; public MasterOfPuppets(Func<string, IPuppet> puppetFactory) { _puppet = puppetFactory("name"); } }
Doing that is the equivalent of using a TypedParameter of type string during the resolution. But, as you can see, that comes from the direct consumer of IPuppet and not something that trickles down through the stack of all resolutions.
Finally, you can also use Autofac modules to do some interesting cross-cutting things the way you see in the log4net integration module example. Using a technique like this allows you to insert a specific parameter globally through all resolutions, but it doesn't necessarily provide for the ability to pass the parameter at runtime - you'd have to put the source of the parameter inside the module.
Point being Autofac supports parameters but not what you're trying to do. I would strongly recommend redesigning the way you're doing things so you don't actually have the need to do what you're doing, or so that you can address it in one of the above noted ways.
Hopefully that should get you going in the right direction.
这篇关于Autofac:使用参数解析依赖项的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!