【问题标题】:Plugin Architecture With Forward & Backward Compatibility具有向前和向后兼容性的插件架构
【发布时间】:2013-03-15 23:11:09
【问题描述】:

我目前正在开发一个将使用插件类型系统的 C# 产品。这不是什么新鲜事,我已经看到很多关于如何使用接口来轻松实现此功能的信息。

我还看到了通过更新接口名称来实现向后兼容性的方法,例如:Interface change between versions - how to manage?

关于主 exe 和插件之间的版本不匹配,我可以预见到我们的产品有多种情况。

  1. 主程序插件版本与插件相同
  2. 主程序比插件更新
  3. 主程序早于插件

根据我收集到的信息,1 和 2 工作得很好。但是我一直无法弄清楚如何正确地实现“前向”兼容性(3)。

我们只打算向插件 API 添加方法。

任何想法都会有很大帮助。

【问题讨论】:

  • “接口”绝对是要走的路。用户可编辑的文本文件也很有帮助,尽管它可能有点矫枉过正/可能超出了您的项目范围。这是一个不错的简单示例:codeproject.com/Articles/6334/Plug-ins-in-C
  • 您在共享 dll 中定义所有接口和 pojo,然后您的插件与该共享 dll 链接,主程序也是如此。然后主程序动态加载插件。为了使其面向未来,您必须编写自定义代码来检测您的主应用程序不知道的所有事情并忽略它们。
  • 您可以使用 MEF (msdn.microsoft.com/en-us/library/dd460648.aspx) 或 MAF (msdn.microsoft.com/en-us/library/bb384200.aspx) 来处理这些问题。 MAF 将能够处理版本控制。 MEF 可以处理版本控制,但您必须提供帮助。
  • 哦 - Windows 版本信息很好 - 您可以/应该将版本信息与所有 .exe 和 .dll 一起使用。只需将其添加到您的项目“构建属性”中。像MEF 这样的废话我不太乐观——我认为你应该让它尽可能简单。另一个好链接:stackoverflow.com/a/2387758/421195

标签: c# plugins backwards-compatibility forward-compatibility


【解决方案1】:

独立的 PluginAPI DLL

首先,您的 PluginAPI(包含接口)应该是主应用程序的独立 DLL。您的主应用程序将引用 PluginAPI,每个插件都将引用 PluginAPI。您很可能已经这样做了。

接口版本控制

其次,在结构上,每次添加新属性或方法时都应该创建一个新接口。

例如:

  1. 版本 1:Plugins.IPerson
  2. 版本 2:Plugins.V2.IPerson:Plugins.IPerson
  3. 版本 3:Plugins.V3.IPerson:Plugins.V2.IPerson

在极少数情况下,您决定删除或完全重新设计您的 API,例如:

  1. 版本 4:Plugins.V4.IPerson //没有任何接口继承

独立的 PluginAPI DLL 版本控制

最后,我不能 100% 确定 PluginAPI .dll 的版本控制将如何进行,即使使用这种接口版本控制的结构架构。它可能有效

您可能需要为每个版本提供匹配的 dll(每个都引用以前的版本)。我们将假设是这种情况。

案例 3 的解决方案

现在让我们来看看你的案例 [3],主程序比插件更老:

  • Person Plugin 实现 Plugins.V2.IPlugin 并引用 V3 .dll(只是为了让它变得有趣)。
  • 主程序引用 V1 .dll
  • 插件文件夹将包含 V2 和 V3 插件 .dll
  • 主应用文件夹将仅包含 V1 插件 .dll(以及其他文件)
  • 主应用程序将通过 IPerson 接口的 V1 定义查找并加载 Person 插件和引用
  • 当然,只有 V1 的方法和属性可以从插件访问到主应用程序
  • (可以通过反射访问其他方法 - 这不是您想要的)

奖励更新

什么时候可以使用插件

  • 第三方扩展您的系统。如果这是一个选项,源代码会更好,或者如果它是基于网络的,则重定向到他们的 URL。这是许多软件项目的梦想,但您应该等到有感兴趣的第三方合作伙伴后再做额外的工作来构建插件框架。
  • 用户可编辑的“脚本”。您不应构建自己的脚本语言,而应针对应用程序域中的限制性接口编译用户 c# 代码,该接口非常受限制(禁用反射等)。
  • 安全分组 - 您的核心软件可能使用受信任的平台调用。风险较高的模块可以被分离到另一个库中,并且最终用户可以选择将其排除在外。

何时不使用插件

我提倡少即是多。不要过度设计。如果您正在构建出色的模块化软件,请使用类和命名空间(不要被接口迷惑)。 “模块化”意味着您正在努力遵守 SOLID 原则,但这并不意味着您需要插件架构。在许多情况下,即使是控制反转也是矫枉过正的。

如果您将来计划向第三方开放,请不要一开始就将其作为插件架构。您可以稍后分阶段构建插件框架:i)派生接口; ii) 在同一个项目中使用接口定义您的插件; iii) 使用插件加载器类加载您的内部插件; iv) 最后,您可以实现一个外部库加载器。这 4 个步骤中的每一个都为您提供了一个独立的工作系统,并将您推向一个完整的插件系统。

热插拔插件

在设计插件架构时,您可能有兴趣知道可以使插件可热插拔:

  1. 不释放内存 - 继续加载新插件。这通常很好,除非它可能是您期望 i) 运行很长时间而不重新启动的服务器软件;并且 ii) 预计在此期间会有许多插件更改和升级。当您在运行时加载插件时,它会将程序集加载到内存中并且无法卸载。请参阅 [2] 了解原因。

  2. 使用释放内存 - 您可以卸载 AppDomain。 AppDomain 在同一进程中运行,但引用隔离 - 您不能直接引用或调用对象。相反,调用必须进行编组,并且数据必须在 appdomain 之间进行序列化。如果您不打算经常更改插件,那么增加的复杂性是不值得的,有:i)由于编组/序列化导致的性能损失,ii)更多的编码复杂性(您不能简单地使用事件、委托和方法像往常一样),iii)这一切都会导致更多的错误并使其更难以调试。

因此,如果选项 [2] 吸引您,请先尝试 [1],然后使用该架构,直到遇到 [2] 所需的问题。永远不要过度架构。相信我,我之前在大学期间已经构建了一个 [2] 架构,这很有趣,但在大多数情况下过度杀伤并且可能会扼杀您的项目(在非业务功能上花费太多时间)。

【讨论】:

    【解决方案2】:

    您需要假设您的插件仅实现公开的接口。如果您发布具有新界面的主程序的新版本,您将检查您的插件是否支持该界面。因此,如果将新插件呈现给旧版本的 main。它要么支持请求的接口,要么不支持并且将作为有效插件失败测试。

    【讨论】:

      猜你喜欢
      • 2011-10-13
      • 2013-12-11
      • 1970-01-01
      • 2011-03-10
      • 1970-01-01
      • 2011-07-08
      • 2021-08-18
      • 1970-01-01
      • 2015-11-25
      相关资源
      最近更新 更多