如何通过构造委托注入依赖项
本文关键字:注入 依赖 何通过 | 更新日期: 2023-09-27 18:32:41
我正在使用具有如下设置结构的第三方库:
IEngine engine = /* singleton provided elsewhere */
var server = new FooServer();
server.AddService("Data1", () => new Data1(engine));
server.AddService("Data2", () => new Data2(engine));
server.Start();
...
server.Dispose();
(lambda 本质上是一个工厂方法;每当它出于自己的目的想要一个新实例时,它都会在内部调用它。
除了进一步的复杂之处在于,我不是直接添加服务,而是使用反射来查找和注册它们,因此只需要定义它们即可工作,而不需要显式列出。 最初我想做这个完全独立的,但是基于反射类型构造通用 lambda 方法似乎太复杂了,所以目前我已经解决了每种类型提供的Register
方法:
class Data1 : DataProvider
{
public static void Register(FooServer server, IEngine engine)
{
server.AddService("Data1", () => new Data1(engine));
}
... (constructor, Dispose, other stuff)
}
var server = new FooServer();
foreach (var type in Utils.GetConcreteTypesWithBase<DataProvider>())
{
var method = type.GetMethod("Register", new[] { typeof(FooServer), typeof(IEngine) });
if (method != null)
{
method.Invoke(null, new object[] { server, engine });
}
// a more ideal approach would be to construct the needed lambda and call
// AddService directly instead of using Register, but my brain fails me.
}
server.Start();
...
server.Dispose();
不用说,这有点丑陋,我相信有更好的方法可以做到这一点。 另一件事是,我已经在使用Castle Windsor来创建IEngine
和其他一些使用它的东西,我想知道如何更好地与之集成。 (目前我只是在这个代码需要它的地方Resolve
引擎 - 它是一个单例,所以生命周期并不棘手。
我真正喜欢的是一种使用方法参数或构造函数注入的方法,以便每个DataProvider
都可以根据其实际依赖项(而不是所有依赖项的并集(拥有一组不同的参数,就像您在 Windsor 控制下所做的那样。 但同样,我什至不知道从哪里开始。 除了基础知识之外,我还没有真正使用过温莎。
请注意,FooServer
、DataProvider
和 AddService<T>(string name, Func<T> factory) where T: DataProvider
方法都在外部代码中,我无法更改它们。 其余的(包括引擎(是我的代码。 再次请注意,我根本不会在代码中创建Data1
实例,而只是一个工厂 lambda,它告诉外部服务器在需要时如何创建它们。
在 qujck 的回答之后,进行了一些必要的编辑,产生了以下代码,供后人使用:
var container = ...;
var server = new FooServer();
foreach (var type in Utils.GetConcreteTypesWithBase<DataProvider>())
{
var t = type; // necessary due to the lambda capturing
container.Register(Component.For(t).LifestyleTransient());
server.AddService(t.Name, () => {
var service = (DataProvider) container.Resolve(t);
service.Closed += (s, e) => container.Release(service);
return service;
});
}
server.Start();
...
server.Dispose();
这符合预期,尽管我仍然对进一步改进它的方法感兴趣。 (我很好奇是否有某种方法可以使用 Castle 自己的 Classes.FromAssembly...
等语法来整理服务的发现和注册,但运气不佳。
您可以定义从容器解析的 lambda。这提供了在一个位置(容器(管理所有服务及其相关生存期的好处。
您需要某种方法来建立每个注册的名称 - 在示例中,我将每个服务注册为类型名称:
[Fact]
public void Configure1()
{
IWindsorContainer container = new WindsorContainer();
var server = new MockFooServer();
container.Register(Component.For<IEngine>().ImplementedBy<Engine>());
foreach (Type type in Utils.GetConcreteTypesWithBase<DataProvider>())
{
container.Register(Component.For(type));
server.AddService(type.Name, () => container.Resolve(type) as DataProvider);
}
var service1 = server.services[typeof(Service1).Name]();
Assert.IsType<Service1>(service1);
}
使用模拟测试FooServer
:
public class MockFooServer
{
public Dictionary<string, Func<DataProvider>> services =
new Dictionary<string, Func<DataProvider>>();
public void AddService<T>(string key, Func<T> factory) where T : DataProvider
{
this.services.Add(key, factory as Func<DataProvider>);
}
}