如何通过构造委托注入依赖项

本文关键字:注入 依赖 何通过 | 更新日期: 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 控制下所做的那样。 但同样,我什至不知道从哪里开始。 除了基础知识之外,我还没有真正使用过温莎。

请注意,FooServerDataProviderAddService<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>);
    }
}