【发布时间】:2010-10-07 06:51:39
【问题描述】:
我正在为我的 .NET 应用程序添加插件支持。基类对我来说听起来很合理,因此人们可以继承它并覆盖某些调用,也可以保留默认功能或可以使用一些内部帮助函数。
为什么我会选择接口而不是基插件类来继承?你能告诉我应该选择哪种设计以及为什么?
【问题讨论】:
标签: .net inheritance plugins interface extensibility
我正在为我的 .NET 应用程序添加插件支持。基类对我来说听起来很合理,因此人们可以继承它并覆盖某些调用,也可以保留默认功能或可以使用一些内部帮助函数。
为什么我会选择接口而不是基插件类来继承?你能告诉我应该选择哪种设计以及为什么?
【问题讨论】:
标签: .net inheritance plugins interface extensibility
您应该考虑查看 System.Addin (MAF) 命名空间或托管可扩展性框架 (MEF)。 MEF 将是首选,即使它仍然是预发布版本,因为它比 MAF 简单得多。这些选择中的任何一个都简化了您需要执行的大量管道工作,以便加载加载项并与它们进行交互。
至于在接口和抽象类之间做出选择,如果您使用 MAF 或 MEF,我们将为您做出一些选择。在此上下文中最基本的区别是,通过提供抽象基类,您可以为自定义插件(由您或其他开发人员编写)提供继承点,并确保某些方法/属性提供默认行为并强制派生类实现某些所需的方法/属性。缺点是您仅限于单个基类。
接口绕过单一基类限制,仍然为自定义插件提供继承点,并确保实现某些方法/属性但不能提供任何类型的默认行为。您也不能在接口中指定除公共成员之外的任何内容,而抽象类可以具有抽象或虚拟非公共成员(通常是受保护的)。
【讨论】:
使用插件合约的接口。如果某种类型的插件可能共享某些功能,请创建一个实现该接口的抽象基类。还要确保您的合同在实际应用程序之外的程序集中。
请记住,在 .NET 中,您没有多重继承。 要求实现者放弃他们的继承槽来为他们提供一点功能不是一个好主意。
【讨论】:
为什么必须是其中一个?
您可以将插件必须遵循的合同定义为一个在任何地方都可以引用的接口。
另外,您还可以定义一个实现该接口的基类,但只有一些抽象方法会被一些您无法为其提供默认实现的接口函数调用。
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 (); }
}
【讨论】:
这里有一篇关于这个主题的好帖子: Plugin API design
我会亲自使用接口,让插件实现所有细节。
【讨论】:
我认为你应该做的显而易见的事情是让插件为它想要处理的单个事件注册回调(委托)。这是大多数框架在 .net 中的工作方式
换句话说,既不是基类也不是接口。此解决方案的优点是您的插件不必符合任何给定的外部接口或基类,它只需注册所需的功能。
例如,任何给定的 asp.net 控件中的“事件”部分都是这样做的:看看UserControl
【讨论】:
每个人都忘记提及的是向后兼容性。您可以在不破坏现有客户端的情况下向基类添加新方法,而不能修改现有接口而不影响它们。
有一些替代方法可以避免接口的兼容性问题。例如,当您为应用程序包含新扩展时,您可以创建一个从旧接口继承的新接口。但这样做的缺点是使用 IClientInterfaceV2 和 IClientInterfaceV3 等类型会使您的架构膨胀。
我个人的建议是采用类似于 scwagner 建议的方法,并同时创建基类和接口。并向接口用户发出警告,说明未来的版本可能会破坏其实现,并且建议使用基类继承策略以避免未来的兼容性问题。
【讨论】: