【问题标题】:Creating plugin instances with context using DI使用 DI 创建带有上下文的插件实例
【发布时间】:2013-03-15 00:02:22
【问题描述】:

我正在重构我们的应用程序以包含依赖注入(通过构造函数注入)并且遇到了一个棘手的极端情况:

我们目前有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 将有助于实现这一点,但看不到如何实现这样的工厂,因为每个插件可能具有不同的构造函数签名。

我该如何解决这种情况?还是我以完全错误的方式处理这个问题?

【问题讨论】:

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


    【解决方案1】:

    所以你有一个ImageViewer,它依赖于ImageViewerPlugin 实例的集合,每个实例都依赖于n 个ImageViewer,它依赖于ImageViewerPlugin 实例的集合,每个实例都依赖于n 个ImageViewer,它依赖于在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;
        }
    });
    

    【讨论】:

    • 谢谢史蒂文。我更新了我的问题以显示我当前如何实例化插件实例。我已经考虑过使用 RegisterInitializer 方法切换到您描述的设计,但是我觉得拥有一个期望通过作为构造函数参数给出其上下文的插件基类是完全有效的。否则感觉就像一个(尽管很小)黑客。通常一个插件在整个应用程序的上下文中运行,在这种情况下它的上下文是隐式的。在这种情况下,它有一个更受限制的上下文,这就是为什么需要告诉它它的上下文是什么。
    • 我查看了您的更新,但我的建议是一样的。防止将此循环引用依赖项与构造函数中的其他依赖项混合。有clever 'hacks' 在这附近,但相反:保持简单,保持快速。
    • 感谢您的回复。虽然我觉得它并不完美,但我会接受你的建议并使用初始化方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-01-05
    • 1970-01-01
    • 1970-01-01
    • 2019-07-24
    • 1970-01-01
    • 1970-01-01
    • 2014-03-20
    相关资源
    最近更新 更多