具有前向和后向兼容性的插件架构

Cam*_*amC 5 c# plugins backwards-compatibility forward-compatibility

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

我还看到了通过更新接口名称来实现向后兼容性的方法,例如:版本之间的接口更改 - 如何管理?

对于主exe和插件之间的版本不匹配,我可以预见多种情况.

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

从信息我已经能够收集1和2工作就好了.但我无法弄清楚如何正确实现"前向"兼容性(3).

我们打算只对插件API添加ADD方法.

任何想法都会有很大的帮助.

Tod*_*odd 3

独立的 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 //没有任何Interface继承

独立的 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 方法和属性可以从插件访问到主应用程序
  • (可以通过反射访问其他方法 - 这不是您想要的)

奖金更新

当你可能使用插件时

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

何时不使用插件

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

如果你打算将来向第三方开放,一开始就不要把它做成插件架构。您可以稍后分阶段构建插件框架:i)派生接口;ii) 在同一项目中定义带有接口的插件;iii) 使用插件加载器类加载您的内部插件;iv) 最后,您可以实现外部库加载器。这 4 个步骤中的每一步都会让您拥有一个独立的工作系统,并将您带向一个完成的插件系统。

热插拔插件

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

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

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

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