深度对象图中的动态依赖 - 我做错了什么?

Sea*_*n U 2 c# dependency-injection castle-windsor typed-factory-facility

我正在尝试修改一些现有代码,以使用Castle Windsor作为IoC容器.

有问题的应用程序是面向文档的.因此,它有一个对象图,它依赖于指定在注册时无法知道的数据源的数据,并且每次解析对象图时都会更改.但是,这种依赖是对象图中的几个层,因此我不能仅将其作为参数传递,container.Resolve()因为Windsor不传播内联依赖项.

我提出的解决方案是使用类型化工厂工具生成一个工厂,它可以解析图中的每个依赖项,并且Document构造函数接受此工厂的实例以及指定数据源的字符串.工厂在注册时指定,数据源在解决时指定.构造函数从那里接管,手动轮询工厂以解决依赖关系.结果看起来像这样:

public interface IDataSource { /* . . . */ }
public interface IWidgetRepository { /* . . . */ }
public interface IWhatsitRepository { /* . . . */ }

// Implementation generated by Windsor's typed factory facility
public interface IFactory
{
    IDataSource CreateDataSource(string path);
    IWidgetRepository CreateWidgetRepository(IDataSource dataSource);
    IWhatsitRepository CreateWhatsitRepository(IDataSource dataSource);

    void Release(IDataSource dataSource);
    void Release(IWidgetRepository widgetRepository);
    void Release(IWhatsitRepository whatsitRepository);
}

public class Document
{
    private readonly IDataSource _dataSource;
    private readonly IWidgetRepository _widgetRepository;
    private readonly IWhatsitRepository _whatsitRepository;

    public Document (IFactory factory, string dataSourcePath)
    {
        _dataSource = factory.CreateDataSource(dataSourcePath);
        _widgetRepository = factory.CreateWidgetRepository(_dataSource);
        _whatsitRepository = factory.CreateWhatsitRepository(_dataSource);
    }
}
Run Code Online (Sandbox Code Playgroud)

这绝对有效,并且确实实现了让Windsor负责依赖性解析的目标.或者至少,我可以通过修改注册码轻松地重新配置应用程序.目前我仍在container.Resolve()为每Document一个被创造的人打电话,但我相信罪可以很容易地用第二个类型工厂修改.

但是,它仍然感觉不对.肯定,温莎负责新产品和(有些)管理他们的生命周期.但它并没有真正将这些依赖注入到Document构造函数中; 相反,构造函数将它们拉出工厂.更糟糕的是,通过将实例传递给IDataSource工厂方法,它负责管理对象图.这似乎是对我的反转的巨大颠覆.

那么,我错过了什么?

编辑

为了澄清,我认为我应该拍摄的是因为Document构造函数看起来更像下面.

public Document (IDataSource dataSource, IWidgetRepository widgetRepository, IWhatsitRepository whatsitRepository)
{
    _dataSource = dataSource;
    _widgetRepository = widgetRepository;
    _whatsitRepository = whatsitRepository;
}
Run Code Online (Sandbox Code Playgroud)

这样,Windsor可以直接控制使用构造函数注入提供所有对象的依赖关系.这实际上是构造函数签名在原始代码中的样子,但我无法让它与Windsor一起使用,因为container.Resolve() 它不会传播内联依赖项参数.所以我不能这样做:

var document = container.Resolve<IDocument>(new { dataSourcePath = somePath });  // throws an exception
Run Code Online (Sandbox Code Playgroud)

因为Windsor没有传递dataSourcePathDataSource构造函数,更不用说确保将DataSource用该路径实例化的函数传递给其他构造函数.

另一个问题的答案指出,这是设计 - 否则会引入耦合.这是禁忌,因为不应该强制或假设接口的实现具有特定的依赖性.遗憾的是,这指出了我认为我提出的代码是错误的另一种方式,因为Factory.CreateWidgetRepository()并且Factory.CreateWhatsitRepository()暗示了这种假设.

Sea*_*n U 9

我相信我找到了正确的解决方案.显然,可用的文档不够明确(冗长?)足以在我读到它的前十二次通过我厚厚的头骨敲打这个概念,所以我会尝试在这里更详细地记录它,以便其他人可能和我一样无助.(我也会在接受之前让它静坐一段时间,希望别人可以提出任何其他/更好的建议.)

长话短说,Typed Factory Facility是错误的工具.

-

诀窍是使用DynamicParameters设施中记录的流畅注册API,这是(相当稀疏)这里,并在最后一节在这里.

DynamicParameters允许您执行的操作是直接修改在要求容器解析组件时提供的内联参数的集合.然后,该字典可以在分辨率管道上传递,使其可用于子依赖项.

DynamicParameters有三个重载,每个重载都以一个委托作为参数.这些委托类型没有明确记录,所以为了后人,这里是ReSharper(我懒得下载源代码.)告诉我他们的声明如下:

public delegate void DynamicParametersDelegate(IKernel kernel, IDictionary parameters);
public delegate ComponentReleasingDelegate DynamicParametersResolveDelegate(IKernel kernel, IDictionary parameters);
public delegate ComponentReleasingDelegate DynamicParametersWithContextResolveDelegate(IKernel kernel, CreationContext creationContext, IDictionary parameters);
Run Code Online (Sandbox Code Playgroud)

DynamicParametersDelegate是最基本的情况,您只需要提供不受容器管理的参数.这可能对我有用,但是为了保持我最先找到复杂选项的倾向,我最终直接选择了第二个选项,即提供一个手动将动态参数从容器中拉出的委托.由于Windsor在这种情况下管理组件的生命周期,因此您需要确保它正在发布.这就是它的位置DynamicParametersResolveDelegate- 它就像第一个一样,除了它还返回一个ComponentReleasingDelegate(public delegate void ComponentReleasingDelegate(IKernel kernel);),Windsor可以在适当的时候释放组件.

(第三个,DynamicParametersWithContextResolveDelegate可能是为了修改创建上下文.我不太了解Windsor如何真正理解前面句子的含义,所以我将不得不留下它.)

这允许我用我看起来更好看的示例替换构造函数:

public class Document
{
    private readonly IDataSource _dataSource;
    private readonly IWidgetRepository _widgetRepository;
    private readonly IWhatsitRepository _whatsitRepository;

    public Document (IDataSource dataSource, IWidgetRepository widgetRepository, IWhatsitRepository whatsitRepository)
    {
        _dataSource = dataSource;
        _widgetRepository = widgetRepository;
        _whatsitRepository = whatsitRepository;
    }
}
Run Code Online (Sandbox Code Playgroud)

工厂完全拆除.相反,魔术在组件注册代码中进行IDocument:

container.Register(Component.For<IDocument>()
                   .ImplementedBy<Document>()
                   .DynamicParameters( 
                       (k, d) =>
                       {
                           // ask for an IDataSource, passing along any inline
                           // parameters that were supplied in the request for 
                           // an IDocument
                           var ds = k.Resolve<IDataSource>(d);

                           // Add it to the dictionary.  This makes it available
                           // for use when resolving other dependencies in the tree.
                           d.Add("DataSource", ds);

                           // Finally, pass back a delegate which can be used to release it
                           return (r) => r.ReleaseComponent(ds);
                       }));
Run Code Online (Sandbox Code Playgroud)

所以现在我可以要求一个IDocument我一直在寻找的代码行:

var document = container.Resolve<IDocument>(new { dataSourcePath = somePath });
Run Code Online (Sandbox Code Playgroud)

并且容器通过调用该DynamicParameters委托开始,该委托为容器提供DataSource用于提供的路径的容器.从那里容器能够自己计算出其余部分,以便将相同的DataSourcegets 实例传递给依赖关系图中所有其他三个对象的构造函数.