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

时间:2014-12-02 08:08:24

标签: c# reflection inversion-of-control castle-windsor

我正在使用具有如下设置结构的第三方库:

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's answer进行一些必要的修改后,会产生以下代码:

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...等语法来整理服务的发现和注册,但是没有多少运气能够解决这个问题。)< / p>

1 个答案:

答案 0 :(得分:2)

您可以定义从容器中解析的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>);
    }
}