扩展/插件通信的架构

6
一旦解决了加载插件的问题(在我们的情况下,通过.NET中的MEF),下一步要解决的是与它们的通信。简单的方法是实现一个接口并使用插件实现,但有时插件只需要扩展应用程序的工作方式,并且可能有很多扩展点。
我的问题是如何处理这些扩展点。我看到了不同的做法,但我不确定每种方法的优缺点,以及是否有更好的方法来完成这个任务:
- 事件:向我们想要“可扩展”的所有内容添加静态事件。例如,如果我想为User类添加自定义验证,我可以添加OnValidation静态事件处理程序,并在构造插件时向其添加事件。 - 消息传递:有一个总线和消息。插件可以订阅特定的消息,并在其他类发布该消息时执行某些操作。消息应包含插件可以工作的上下文。在验证案例中,逻辑层将发布一个UserValidation消息,并在收到该消息时插件将执行操作。 - 接口:主机应用程序负责调用所有实现某些接口的插件,并向它们提供当前操作的上下文。在验证的情况下,插件可以使用Validate(object context)方法实现IValidator或IUserValidator接口。
您是否使用过其中一种方法?哪种方法对您最有效?
在您问之前,我们的应用程序是可扩展的核心(用户、角色和内容管理),构建在ASP.NET MVC之上的特定于客户端的内容中心Web应用程序。
2个回答

2
你在设计决策时的关键是分析并清楚地了解插件之间的差异。例如,在处理静态事件时,你可能需要将每个事件定义为令牌、枚举、对象等形式。为每个插件定义一个新的事件集合自然会反对你的整个设计,特别是在松散耦合和重用方面。
如果插件非常不同,那么使用总线/消息体系结构可能会有所裨益,因为在这种情况下,你可以引入通信交换的领域/类别,插件可以订阅它们。也就是说,一系列事件和消息可以处于某种利益领域。请注意,特定类别内部的通信仍然可以利用静态事件,因此这两种选择并不互斥。
直接由插件实现的接口是我在插件架构方面经验最严格的方法。扩展插件接口通常意味着必须在插件和提供者的代码中进行修改。你需要一个坚实的通用接口来确保应用程序能够长期运行。
将设计分解成两个方面——通信通道协议——可能更容易处理。静态事件处理是一个协议问题,而总线消息和直接接口是一个通道问题。
从一开始就正确设计协议通常是最困难的,因为你可能无法很好地了解如何绘制线条的一般或特定性。
在他的评论中,Lars提出了一个重要观点——如果你的平台支持异常处理,那么使用直接接口时可以集中大量错误处理,从而减轻插件处理通用错误(例如“插件加载错误”或“文件打开失败”)的负担。然而,如果每次添加插件都必须维护接口,这样的好处似乎会消失。最糟糕的情况是当接口在途中开始变得不一致时,因为你没有意识到它们应该从一开始就支持什么。在已经创建了相当数量的插件之后重构整个接口设计并不容易。

接口可能是我的选择,但这取决于应用程序及其插件需求。 但这是一种非常干净的插件方式,也很容易将异常从插件中隔离出来。 - Lars Mæhlum

0
我会选择“观察者”模式。来自GOF的定义:
定义对象之间的一对多依赖关系,以便当一个对象改变状态时,所有依赖它的对象都会被通知并自动更新。
也被称为发布-订阅模式,我建议它最符合你例子中的第二种情况。

在我看来,观察者模式比这个问题高一个层次。也就是说,他提出的所有替代方案都可以适用于该模式。而手头的问题更多地与观察者模式的实现特定决策有关。 - sharkin
再次阅读并深入思考后,我认为你可能是正确的R.A。 - Martin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接