插件系统设计与实现

随着应用程序的增长,它们的维护变得越来越困难,即使大多数应用程序从一开始就设计得相当好。这通常是因为在应用程序中添加新功能时很容易增加耦合度。插件是解决这个问题的一种方式,通过在小型可测试的DLL中添加新功能。

本文不包含大量描述性文本,而只是展示如何使用插件系统。如果有任何想法,请在文章下方评论或访问讨论板。

插件系统

插件系统由三部分组成:

应用程序创建PluginManager的实例,用于查找和加载插件。通过指定包含目录信息和文件通配符的搜索字符串来找到插件。可以指定每个程序集应加载的安全设置(Evidence),这非常适合于插件具有不同访问级别。

第二个程序集包含应用程序向插件公开的接口。这个程序集的存在是因为可能不希望将整个应用程序发送给第三方插件开发者。

最后是实际的插件程序集。插件可以标记自己依赖于其他插件。例如,假设有一个生成并发送新闻稿的新闻稿插件。新闻稿插件需要一个在另一个插件中指定的邮件发送器。因此,新闻稿插件可以标记自己依赖于邮件发送器插件,结果邮件发送器将始终在新闻稿插件之前加载。

示例

在本文中,将演示如何创建一个简单的插件。首先,需要指定插件应该能够做什么,并定义应用程序的哪些部分可以从插件中访问。所有这些都被添加到一个类库中,该类库将发送给插件开发者。

在这个示例中,应用程序希望让插件能够注册组件并为主应用程序菜单添加菜单项。通过让插件注册组件,得到了一个相当灵活的解决方案,因为插件可以使用其他插件注册的组件。

public interface IMyApplication : IApplication { void AddMenuItem(string name, MenuItemClickedHandler clickHandler); void RegisterComponent(T component) where T : class; T GetComponent() where T : class; } public delegate void MenuItemClickedHandler(string name);

插件应该实现的接口:

public interface IMyPlugin : IPlugin { void SayHelloTo(string name); }

对插件的期望不高,它们只需要向告诉它们的任何人打招呼。

插件有一个它将注册的组件,从而为应用程序添加功能,该功能可以由应用程序本身或其他插件使用。

public interface IWorldPeace { void Initiate(); }

插件包含两部分:首先实现插件接口的类,然后是插件引入的实现IWordPeace的类。

public class MyPlugin : IMyPlugin { private const string _pluginName = "MyPlugin"; private readonly IPluginInfo _pluginInfo = new MyPluginInfo(); private readonly IEnumerable _dependencies = new List(); public string PluginName { get { return _pluginName; } } public IPluginInfo PluginInfo { get { return _pluginInfo; } } public IEnumerable Dependencies { get { return _dependencies; } } public void Start(IApplication application) { IMyApplication myApplication = (IMyApplication) application; myApplication.RegisterComponent(new WorldPeaceThroughLove()); myApplication.AddMenuItem("Click me", OnClick); } private void OnClick(string name) { Console.WriteLine("Omg! You clicked me!"); } public void SayHelloTo(string name) { Console.WriteLine("Hello " + name); } } public class WorldPeaceThroughLove : IWorldPeace { public void Initiate() { Console.WriteLine("Love is all around!"); } }

在本示例中使用控制台应用程序,但它可以是Winforms或Web应用程序。

public class Program : IApplication { private PluginManager _pluginManager; private readonly IDictionary _components = new Dictionary(); [STAThread] public static void Main() { new Program().Start(); } public void AddMenuItem(string name, MenuItemClickedHandler clickHandler) { // here we should add the menu item to a real application } public void RegisterComponent(T component) where T : class { _components.Add(typeof(T), component); } public T GetComponent() where T : class { return (T)_components[typeof(T)]; } private void Start() { // Create a plugin manager and load it _pluginManager = new PluginManager(this); // plugins are in the same folder as the application and // their file names end with "Plugin.dll" _pluginManager.PluginPath = "*Plugin.dll"; _pluginManager.Start(); // Call a method defined in the plugin _pluginManager["MyPlugin"].SayHelloTo("everyone!"); // Access a registered component. IWorldPeace worldPeace = GetComponent(); worldPeace.Initiate(); } }

更新:卸载插件

收到了一些关于如何卸载插件的问题。插件系统中没有支持这个功能,将尝试解释为什么。

首先,没有办法从应用程序域(应用程序)中卸载程序集(DLL)。没有。

尽管有一种解决方法:使用所有插件的辅助应用程序域,这意味着插件在可以被认为是外部应用程序的东西中(每个应用程序域都有隔离的内存,辅助应用程序域中的未处理异常不会影响主应用程序域),这意味着需要使用远程处理才能在插件和主应用程序之间进行通信。

将所有插件放在辅助应用程序域中意味着不能卸载单个插件,必须卸载所有插件。但是嘿,可以为每个插件使用一个应用程序域,对吧?当然可以;如果能忍受性能损失的话。

假设有一个插件,它为应用程序的其余部分提供带有联系人的地址簿。真的想卸载它吗?假设应用程序(或任何其他插件)包含对一些联系人的引用,如果卸载该插件,它们会发生什么?

卸载东西比想象的要复杂得多。

就是这样。如果有不明白的地方,请告诉。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485