使用DI创建具有上下文的插件实例

时间:2013-03-15 00:02:23

标签: c# plugins dependency-injection simple-injector

我正在重构我们的应用程序以包含依赖注入(通过构造函数注入)并遇到了棘手的问题:

我们当前有ImageViewer个对象,在实例化时会在程序集中搜索ImageViewerPlugin(抽象基类)实例,并使用反射对它们进行实例化。这是在ImageViewer的构造函数中使用方法(在所有具体插件类型的循环中调用)完成的,类似于:

private ImageViewerPlugin LoadPlugin(Type concretePluginType)
{
  var pluginConstructor = concretePluginType.GetConstructor(
    BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public,
    null,
    new[] { typeof(ImageViewer) },
    null);

  return (ImageViewerPlugin) pluginConstructor.Invoke(
    new object[] { constructorParameter });
}

ImageViewerPlugin类看起来大致如下:

internal ImageViewerPlugin
{
  protected ImageViewer _viewer;
  protected ImageViewerPlugin(ImageViewer viewer)
  {
    _viewer = viewer;
  }
}

具体实现大致如下:

internal AnImageViewerPlugin
{
  public AnImageViewerPlugin(ImageViewer viewer) : base(viewer)
  {
  }
}

每个ImageViewer实例都有自己的ImageViewerPlugin个实例集合。

现在重构的应用程序使用DI容器和构造函数注入我发现这些插件具有依赖性(以前通过使用全局静态类隐藏)需要由DI容器解析,但是如果不使用服务定位器(反模式),我不知道该怎么做。

最明智的解决方案似乎是使用DI创建这些插件实例。这将允许我添加额外的构造函数参数,以使它们依赖于通过构造函数注入注入的依赖项。但是,如果我这样做,如何在注入其余参数值的同时传递特定的viewer参数值?

我认为ImageViewerPluginFactory有助于实现这一点,但无法看到如何实现这样的工厂,因为每个插件可能都有不同的构造函数签名。

我该如何解决这个问题?还是我完全错误地接近这个?

1 个答案:

答案 0 :(得分:2)

所以你有一个ImageViewer取决于ImageViewerPlugin个实例的集合,每个实例依赖于n ImageViewer,这取决于ImageViewerPlugin个实例的集合,每个取决于取决于ImageViewer个实例集合的n ImageViewerPlugin,每个实例都取决于......你得到的图片是: - )

这是循环参考。将整个DI容器放在一边,在手动执行此操作时如何创建此层次结构?使用构造函数注入,您不能。从以下示例可以看出:

var plugin = new ImageViewerPlugin( [what goes here?] );
var viewer = new ImageViewer(plugin);

你必须以某种方式打破这种依赖循环,一般的建议是在这种情况下依赖于属性注入:

var plugin = new ImageViewerPlugin();
var viewer = new ImageViewer(plugin);

// Property injection
plugin.Viewer = viewer;

但更重要的是,您应该仔细研究应用程序的设计,因为循环引用通常表示设计中存在问题。例如,仔细查看插件从查看器需要的行为以及查看器从插件中需要的行为。您可以将其提取到另一个类,如下所示:

var imageServices = new ImageServices();
var plugin = new ImageViewerPlugin(imageServices);
var viewer = new ImageViewer(imageServices, plugin);

这完全解决了这个问题,但这取决于你的情况是否可行。

使用最后的解决方案,使用Simple Injector进行注册将非常简单。使用属性注入破坏依赖关系时,可以使用RegisterInitializer(Action)方法。它允许您打破依赖循环。例如:

container.RegisterInitializer<ImageViewer>(viewer =>
{
    foreach (var plugin in viewer.Plugins)
    {
        plugin.Viewer = viewer;
    }
});