171

在我的开发工作中,一个重复的主题是使用或创建内部插件架构。我已经看到它采用了很多方法——配置文件(XML、.conf 等)、继承框架、数据库信息、库等等。在我的经验中:

  • 数据库不是存储配置信息的好地方,尤其是与数据混合的地方
  • 尝试使用继承层次结构需要有关要编码的插件的知识,这意味着插件架构并不是那么动态的
  • 配置文件可以很好地提供简单的信息,但不能处理更复杂的行为
  • 库似乎运行良好,但必须仔细创建单向依赖项。

当我试图从我使用过的各种架构中学习时,我也在向社区寻求建议。您是如何实现 SOLID 插件架构的?你最糟糕的失败是什么(或者你见过的最糟糕的失败)是什么?如果你要实现一个新的插件架构,你会怎么做?您使用过的哪个 SDK 或开源项目具有良好架构的最佳示例?

我自己找到的几个例子:

这些例子似乎发挥了各种语言的优势。一个好的插件架构是否必须与语言相关联?最好使用工具来创建插件架构,还是在自己的以下模型上进行?

4

9 回答 9

96

这不是一个答案,而是一堆可能有用的评论/示例。

  • 使您的应用程序可扩展的一种有效方法是将其内部作为脚本语言公开,并用该语言编写所有顶级内容。这使得它非常可修改并且实际上是面向未来的(如果您的原语选择和实施得当)。这类事情的一个成功案例是 Emacs。我更喜欢 Eclipse 风格的插件系统,因为如果我想扩展功能,我不必学习 API 并编写/编译单独的插件。我可以在当前缓冲区本身中编写一个 3 行代码段,对其进行评估并使用它。非常流畅的学习曲线和非常令人满意的结果。

  • 我稍微扩展的一个应用程序是Trac。它有一个组件架构,在这种情况下意味着任务被委派给宣传扩展点的模块。然后,您可以实现适合这些点的其他组件并更改流程。这有点像上面 Kalkie 的建议。

  • 另一个很好的是py.test。它遵循“最好的 API 就是没有 API”的理念,并且完全依赖于在每个级别调用的钩子。您可以在根据约定命名的文件/函数中覆盖这些钩子并更改行为。您可以在网站上查看插件列表,了解它们的实施速度/容易程度。

一般几点。

  • 尽量保持您的不可扩展/非用户可修改的核心尽可能小。将您所能做的一切委托给更高的层,以增加可扩展性。在出现错误选择的情况下,需要在核心中纠正的东西更少。
  • 与上述观点相关的是,你不应该在一开始就对项目的方向做出太多决定。实现所需的最小子集,然后开始编写插件。
  • 如果您要嵌入一种脚本语言,请确保它是一种完整的语言,您可以在其中编写通用程序,而不是用于您的应用程序的玩具语言。
  • 尽可能减少样板。不要为子类化、复杂的 API、插件注册和类似的东西而烦恼。尽量保持简单,使其易于扩展,而不仅仅是可以扩展。这将使您的插件 API 得到更多使用,并鼓励最终用户编写插件。不仅仅是插件开发人员。py.test 做得很好。据我所知,Eclipse没有
于 2010-05-07T12:29:43.003 回答
27

根据我的经验,我发现实际上有两种类型的插件架构。

  1. 一个遵循Eclipse model旨在允许自由并且是开放式的。
  2. 另一个通常需要插件遵循 anarrow API因为插件将填充特定功能。

换一种说法,一种允许插件访问您的应用程序,另一种允许您的应用程序访问插件

区别很微妙,有时没有区别……您的应用程序都需要两者。

我在 Eclipse/Opening up your App to plugins 模型方面没有大量经验(Kalkie 帖子中的文章很棒)。我读过一些关于 eclipse 做事的方式,但仅此而已。

Yegge 的属性博客谈到了如何使用属性模式来实现插件和可扩展性。

我所做的大部分工作都使用了插件架构来允许我的应用程序访问插件,例如时间/显示/地图数据等。

几年前,我会创建工厂、插件管理器和配置文件来管理所有这些,并让我决定在运行时使用哪个插件。

  • 现在我通常只做DI framework大部分工作。

我仍然需要编写适配器才能使用第三方库,但它们通常还不错。

于 2010-05-07T16:28:26.977 回答
14

我见过的最好的插件架构之一是在 Eclipse 中实现的。与具有插件模型的应用程序不同,一切都是插件。基础应用程序本身就是插件框架。

http://www.eclipse.org/articles/Article-Plug-in-architecture/plugin_architecture.html

于 2010-05-04T20:38:06.493 回答
12

我将描述一个我过去使用过的相当简单的技术。这种方法使用 C# 反射来帮助插件加载过程。可以修改此技术,使其适用于 C++,但您会失去使用反射的便利。

IPlugin接口用于识别实现插件的类。方法被添加到接口以允许应用程序与插件通信。例如Init,应用程序将用来指示插件初始化的方法。

要查找插件,应用程序会扫描插件文件夹中的 .Net 程序集。每个程序集都已加载。反射用于扫描实现IPlugin. 创建每个插件类的实例。

(或者,Xml 文件可能会列出要加载的程序集和类。这可能有助于提高性能,但我从未发现性能问题)。

Init为每个插件对象调用该方法。它传递了对实现应用程序接口的对象的引用:(IApplication或其他特定于您的应用程序的名称,例如 ITextEditorApplication)。

IApplication包含允许插件与应用程序通信的方法。例如,如果您正在编写文本编辑器,则此接口将具有OpenDocuments允许插件枚举当前打开文档的集合的属性。

LuaPlugin通过创建一个派生的插件类,例如将IPlugin功能和应用程序接口转发到 Lua 脚本,这个插件系统可以扩展到脚本语言,例如 Lua 。

这种技术允许您在开发期间迭代地实现您的IPluginIApplication其他特定于应用程序的接口。当应用程序完成并很好地重构时,您可以记录您公开的接口,并且您应该有一个很好的系统,用户可以为其编写自己的插件。

于 2010-05-13T12:38:19.327 回答
7

我曾经参与过一个项目,该项目必须让每个客户设置系统的方式都非常灵活,我们发现的唯一好的设计就是为客户提供 C# 编译器!

如果规范中充满了这样的词:

  • 灵活的
  • 插入
  • 可定制

根据我的经验,问很多关于您将如何支持系统的问题(以及如何收取支持费用,因为每个客户都会认为他们的情况是正常情况,不应该需要任何插件。)

客户(或源代码支持人员)编写插件的支持比架构困难得多

于 2010-05-10T12:42:36.317 回答
6

通常我使用MEF。托管可扩展性框架(或简称 MEF)简化了可扩展应用程序的创建。MEF 提供可用于加载应用程序扩展的发现和组合功能。

如果您有兴趣阅读更多...

于 2010-05-04T19:13:18.070 回答
5

根据我的经验,创建灵活的插件架构的两种最佳方法是脚本语言和库。这两个概念在我看来是正交的;两者可以以任何比例混合,就像函数式编程和面向对象编程一样,但在平衡时会发现它们的最大优势。库通常负责实现具有动态功能的特定界面,而脚本倾向于强调具有动态界面的功能。

我发现基于脚本管理库的架构似乎效果最好。脚本语言允许对较低级别的库进行高级操作,因此这些库从任何特定的接口中解放出来,将所有应用程序级别的交互留在脚本系统更灵活的手中。

为此,脚本系统必须具有相当健壮的 API,与应用程序数据、逻辑和 GUI 挂钩,以及从库中导入和执行代码的基本功能。此外,通常要求脚本是安全的,因为应用程序可以从编写不佳的脚本中优雅地恢复。使用脚本系统作为间接层意味着应用程序可以更容易地分离自身,以防出现 Something Bad™。

打包插件的方式很大程度上取决于个人喜好,但是使用简单界面的压缩存档(例如PluginName.ext在根目录中)永远不会出错。

于 2010-05-12T16:54:44.017 回答
3

我认为您需要首先回答这个问题:“哪些组件应该是插件?” 您希望将此数字保持在绝对最小值,否则您必须测试的组合数量会爆炸。尝试将您的核心产品(不应该有太多的灵活性)与插件功能分开。

我发现 IOC(控制反转)主体(阅读 springframework)可以很好地提供灵活的基础,您可以添加专业化以简化插件开发。

  • 您可以扫描容器中的“接口作为插件类型广告”机制。
  • 您可以使用容器来注入插件可能需要的常见依赖项(即 ResourceLoaderAware 或 MessageSourceAware)。
于 2010-05-12T16:33:36.860 回答
1

插件模式是一种软件模式,用于扩展具有干净接口的类的行为。类的行为通常通过类继承来扩展,派生类会覆盖类的一些虚方法。这个解决方案的一个问题是它与实现隐藏冲突。它还导致派生类成为不相关行为扩展的聚集地的情况。此外,如上所述,脚本用于实现这种模式“将内部作为一种脚本语言并用该语言编写所有顶级内容。这使得它非常可修改并且实际上是面向未来的”。库使用脚本管理库。脚本语言允许对低级库进行高级操作。(也如上所述)

于 2020-03-03T16:47:46.213 回答