隔离的 PluginAPI DLL
首先,您的 PluginAPI(包含接口)应该是主应用程序的独立 DLL。您的主应用程序将引用 PluginAPI,每个插件都将引用 PluginAPI。你很可能已经在这样做了。
接口版本控制
其次,在结构上,每次添加新属性或方法时都应该创建一个新接口。
例如:
- 版本 1:Plugins.IPerson
- 版本 2:Plugins.V2.IPerson:Plugins.IPerson
- 版本 3:Plugins.V3.IPerson:Plugins.V2.IPerson
在极少数情况下,您决定删除或完全重新设计 API,例如:
- Version 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 个步骤中的每一个都为您提供了一个独立的工作系统,并将您推向一个完整的插件系统。
热插拔插件
在设计插件架构时,您可能有兴趣知道可以使插件可热插拔:
无需释放内存 - 只需继续加载新插件即可。这通常很好,除非它可能是您期望 i) 运行很长时间而不重新启动的服务器软件;并且 ii) 预计在此期间会有许多插件更改和升级。当您在运行时加载插件时,它会将程序集加载到内存中并且无法卸载。参见 [2] 了解原因。
使用释放内存 - 您可以卸载 AppDomain。AppDomain 在同一进程中运行,但引用隔离 - 您不能直接引用或调用对象。相反,调用必须进行编组,并且数据必须在 appdomain 之间进行序列化。如果您不打算经常更改插件,那么增加的复杂性是不值得的,有:i)由于编组/序列化导致的性能损失,ii)更多的编码复杂性(您不能简单地使用事件、委托和方法像往常一样),iii)这一切都会导致更多的错误并使调试更加困难。
因此,如果选项 [2] 吸引您,请先尝试 [1],然后使用该架构,直到遇到 [2] 所需的问题。永远不要过度架构。相信我,我之前在大学期间构建了一个 [2] 架构,这很有趣,但在大多数情况下过度杀伤并且可能会扼杀你的项目(在非业务功能上花费太多时间)。