添加插件支持:接口还是基类继承?

3
我正在给我的.NET应用程序添加插件支持。对我来说,一个基类听起来很合理,这样人们就可以继承它并覆盖某些调用,同时也可以保留默认功能或使用一些内部帮助函数。
为什么我会选择接口而不是基插件类来继承?你能告诉我应该选择哪种设计以及为什么吗?
6个回答

5
你应该考虑查看 System.Addin (MAF) 命名空间或 Managed Extensibility Framework (MEF)。即使 MEF 还处于预发布阶段,但它比 MAF 更简单,因此 MEF 是首选。这两个选择都可以简化您需要执行的插件加载和交互的大量工作。
至于在接口和抽象类之间做出选择,如果您选择 MAF 或 MEF,则其中一些内容将由它们自动处理。在这种情况下,最基本的区别是,通过提供抽象基类,您为自定义插件(由您或其他开发人员编写)提供了继承点,并确保某些方法/属性提供默认行为并强制派生类实现某些必需的方法/属性。缺点是您受到单个基类的限制。
接口避免了单个基类的限制,仍然为自定义插件提供继承点,并确保实现某些方法/属性,但不能提供任何类型的默认行为。您还不能在接口中指定除公共成员以外的任何内容,而抽象类可以具有抽象或虚拟的非公共成员(通常是受保护的)。

这听起来很有趣,你知道有什么好的文章可以快速入门吗?因为现在当我看它时,它看起来非常复杂 :) - dr. evil
我认为这是一个不错的开始: http://www.codeplex.com/MEF/Wiki/View.aspx?title=Hosting%20MEF%20and%20the%20Container&referringTitle=Guide - 我会进一步研究,感谢您的回答。 - dr. evil
我刚刚实现了这个功能,比我想象的要容易得多(除了我在第一次尝试中犯了一些愚蠢的错误 :) )。 - dr. evil
@Slough:很高兴你能让一切都正常工作了。是的,MEF使得向应用程序添加插件支持变得更加容易。 - Scott Dorman
另一个选择是Mono.Addins。如果您需要更高级的插件处理功能,例如插件依赖项和元数据,则它是一个很好的选择(请参见http://monoaddins.codeplex.com)。 - Lluis Sanchez

4

使用接口来定义插件的契约。如果某种类型的插件可能共享某些功能,请制作一个实现该接口的抽象基类。同时确保您的合同位于实际应用程序之外的程序集中。

请记住,在.NET中,您不能进行多重继承。强制实现者放弃他们的继承位置为他们提供一点功能并不是一个好主意。


哇,多重继承的限制是一个我从未想过的好点子,谢谢你提醒。 - dr. evil

4

为什么要选择一个而不是另一个呢?

您可以将插件必须遵循的契约定义为接口,并在各处引用它。

此外,您还可以定义一个基类来实现该接口,但仅具有调用某些接口函数的抽象方法,因为您无法为其中一些接口函数提供默认实现。

public interface IPlugin
{
    void Initialize ();
    void Stuff ();
}

public class PluginBase : IPlugin
{
    protected abstract DoInitialize ();
    protected abstract DoStuff ();

    void IPlugin.Initialize { this.DoInitialize (); }
    void IPlugin.Stuff { this.DoStuff (); }
}

你为什么要增加PluginBase类的复杂性和开销?这对我来说似乎非常无用,而且容易出错。 - strager
@strager,最初我想这样做是为了能够在基类中提供一些额外的标准函数。 - dr. evil
啊,好的,我现在明白了。不过这个例子并没有很好地展示出来。 - strager
不好意思,这个例子很糟糕,因为它根本没有展示任何默认功能。 - scwagner

2
这个主题有一个不错的帖子在这里:插件API设计 我个人会使用接口,并让插件实现所有细节。

@Jon Tackabury:我们仍在摇摆不定,是接口、抽象还是属性;然而,插件(即最终用户)将不得不实现所有细节。 - IAbstract

1

我认为你应该做的显而易见的事情就是让插件为它想要处理的个别事件注册回调(委托)。这是大多数 .net 框架工作的方式。

换句话说,既没有基类也没有接口。这种解决方案的优点是,插件不必遵守任何给定的外部接口或基类,它只需注册所需的功能即可。

例如,任何给定 asp.net 控件中的“事件”部分都是这样做的:看看 UserControl


这种方法与接口或继承相比有什么优势吗?因为对我来说,这听起来比实现接口要复杂一些。 - dr. evil
我稍微编辑了一下。也许我没有完全表达清楚,但我所说的是在 .net 中进行此操作的标准方法。 - krosenvold
+1 用于插件注册回调 - cha-ching - 我已经开发了一个允许异步委托注册的类。 - IAbstract

1

每个人都忽略了向后兼容性这一点。您可以向基类添加新方法而不会破坏现有客户端,但是您不能修改现有接口而不影响它们。

有避免接口兼容性问题的替代方案。例如,当您包括新的应用程序扩展时,可以创建一个从旧接口继承的新接口。但是这样做会使您的体系结构膨胀,并产生像 IClientInterfaceV2 和 IClientInterfaceV3 这样的类型。

我个人的建议是采用像 scwagner 建议的方法,同时创建基类和接口,并向接口用户发出警告,指出未来版本可能会破坏其实现,并且基类继承策略是避免未来兼容性问题的推荐策略。


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