如何创建灵活的插件架构?


154

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

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

当我寻求与我合作过的各种体系结构学习时,我也在寻求社区的建议。您如何实现SOLID插件架构?您最严重的失败是什么(或您所见过的最严重的失败)?如果要实现新的插件体系结构,该怎么办?您所使用的哪个SDK或开源项目具有良好架构的最佳示例?

我自己发现了一些示例:

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


你能说为什么插件架构一直是一个普遍的主题吗?它解决了什么问题,或解决了什么目标?许多可扩展的系统/应用程序都使用某种形式的插件,但是形式不同,取决于要解决的问题。
mdma 2010年

@mdma-这是一个很好的问题。我想说的是,可扩展系统中有一些共同的目标。也许目标是所有共同点,并且最佳解决方案因系统需要具有多大的可扩展性,系统以何种语言编写等等而异。但是,我看到像IOC这样的模式已被应用到许多语言中,并且我被要求一遍又一遍地制作类似的插件(用于响应相同功能请求的片段)。我认为最好对各种插件的最佳做法有一个总体了解。
justkt

Answers:


89

这并不是一堆可能有用的注释/示例的答案

  • 使应用程序可扩展的一种有效方法是将其内部结构作为脚本语言公开,并用该语言编写所有顶级内容。这使得它具有相当大的可修改性,并且几乎可以作为将来的证明(如果您的原语经过精心选择和实施)。这种事情的成功故事是Emacs。与eclipse风格的插件系统相比,我更喜欢这样做,因为如果我想扩展功能,则不必学习API并编写/编译单独的插件。我可以在当前缓冲区本身中编写一个3行代码段,对其进行评估并使用它。非常平滑的学习曲线和令人愉悦的结果。

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

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

一些一般要点。

  • 尝试使您的不可扩展/用户不可修改的核心尽可能小。将所有内容委托给更高的层,以增加可扩展性。如果选择错误,则可以减少核心中要纠正的内容。
  • 与上述观点有关的是,一开始就不应对项目的方向做出太多决定。实现所需的最小子集,然后开始编写插件。
  • 如果要嵌入脚本语言,请确保它是一种完整的脚本语言,您可以在其中编写通用程序,而不能为应用程序编写玩具语言。
  • 尽可能减少样板。不用担心子类化,复杂的API,插件注册以及类似的事情。尽量保持简单,所以它的方便,而不仅仅是可能延长。这将使您的插件API得到更多使用,并鼓励最终用户编写插件。不只是插件开发人员。py.test做得很好。据我所知,Eclipse 没有

1
我将脚本语言的要点扩展到,值得考虑嵌入现有的语言(例如python或perl)
Rudi 2010年

真正的鲁迪。这些语言被设计成可嵌入的许多语言。例如,Guile仅用于嵌入。如果您要使应用程序可编写脚本,则可以节省时间。您可以只使用其中一种语言。
努法尔·易卜拉欣

24

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

  1. 紧随Eclipse model其后的意思是允许自由并且是开放式的。
  2. 另一个通常要求插件遵循a,narrow API因为插件将填充特定的功能。

为了以不同的方式说明这一点,一个允许插件访问您的应用程序,而另一个允许您的应用程序访问插件

两者之间的区别很细微,有时甚至没有区别。

我对Eclipse /打开您的插件应用程序模型没有很多经验(Kalkie帖子中的文章很棒)。我已经了解了Eclipse的工作方式,但仅此而已。

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

我完成的大部分工作都使用了插件架构,以允许我的应用访问插件,时间/显示/地图数据等内容。

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

  • 现在,我通常只需DI framework完成大部分工作即可。

我仍然必须编写适配器才能使用第三方库,但是它们通常并不算糟糕。


@Justin是的,DI =依赖注入。
推导

14

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

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


当然是一个很好的例子,但是它比许多应用程序所需的更大,更复杂吗?
justkt

是的,灵活性的增加是有代价的。但是我认为这显示了不同的观点。为什么您的应用程序菜单不能是插件或主视图?
Patrick

6
在新版本的Symfony框架中使用了相同的方法(一切都是插件)。它们称为捆绑,但原理相同。它的架构非常简单,也许您应该看一下:symfony-reloaded.org/quick-tour-part-4
Marijn Huizendveld 2010年

@Marjin Huizendveld-您应该将其添加为单独的答案,以便我可以对答案进行投票。看起来像一个有趣的框架!
justkt 2010年

11

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

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

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

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

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

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

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

这种技术可以让你反复地实现你的IPluginIApplication在开发过程中和其他应用程序特定的接口。当应用程序完成并且可以很好地重构后,您可以记录暴露的接口,并且应该有一个不错的系统,用户可以为其编写自己的插件。


7

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

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


谢谢,看起来很有趣。在其他语言中应用相似的主体有多容易?
justkt 2010年

实际上,MEF仅适用于.NET框架,我对其他框架
一无所知

7

我曾经从事过一个项目,该项目必须在每个客户可以设置系统的方式上如此灵活,我们发现的唯一好的设计就是为客户提供C#编译器!

如果规格中填满以下字词:

  • 灵活
  • 插入
  • 可订制

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

客户(或最初的支持人员)编写插件的支持要比体系结构难得多


5

以我的经验,创建灵活的插件体系结构的两种最佳方法是脚本语言和库。我认为这两个概念是正交的。两者可以按任意比例混合使用,就像函数式编程和面向对象的编程一样,但是在平衡时找到了自己的最大优势。库通常负责实现具有动态功能的特定接口,而脚本则倾向于强调具有动态接口的功能

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

为此,脚本系统必须具有相当健壮的API,并具有对应用程序数据,逻辑和GUI的钩子,以及从库中导入和执行代码的基本功能。此外,通常要求脚本是安全的,因为应用程序可以从编写不当的脚本中正常恢复。将脚本系统用作间接层意味着应用程序可以在Something Bad™情况下更轻松地分离自身。

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


3

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

我发现IOC(控制反转)主体(读取springframework)非常适合提供灵活的基础,您可以添加专门知识以简化插件开发。

  • 您可以在容器中扫描“接口作为插件类型广告”机制。
  • 您可以使用容器注入插件可能需要的常见依赖项(即ResourceLoaderAware或MessageSourceAware)。

1

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.