接口与抽象类






4.22/5 (8投票s)
接口和抽象类的用法。
接口和抽象类:
介绍
正如我们所知的多重继承,它是将其基类功能提供给派生类的能力。我们经常讨论接口是实现 C# 中多重继承的正确解决方案,因为 C# 不直接支持多重继承,那么我们就会想,接口只包含实现,它如何帮助实现多重继承?为什么如果我们能通过抽象类或其他方式做到这一点,还需要接口?简而言之,如果你需要派生类共享行为,则使用抽象类;如果你需要可插入的元素结构,则使用接口,因为接口只是可重用组件设计中多重继承的替代方案,而非共享功能,它只是为派生类定义了一个契约。
抽象类和接口用于在代码中设置抽象层。
相似之处
- 抽象类和接口不能直接实例化。
- 像接口一样,抽象类也可以是声明性的,如果所有成员都定义为抽象。
区别
- 抽象类可以有或没有抽象方法,而接口只能是声明性的。
- 抽象类可以包含变量,而接口则不能。
- 接口成员默认为 public;所以你不能使用访问修饰符来限制访问,而抽象类遵循常规类的规则。
- 除了抽象类可以使用的静态和常量成员外,接口不能使用。
- 一个类或结构可以继承自多个接口,但只能继承自一个抽象类。
但困惑依然存在:为什么、在哪里以及如何使用这些类型?接口又是如何成为多重继承的替代方案的?
接口包含一组方法和成员的规范。我们都知道基类允许在派生类中构建共享行为,当所有派生类都需要具有与其他类相关的相同功能时,这很有用。接口允许你跨越不共享通用子类的类来创建通用行为,这意味着我们可以向上转换实现接口的任何对象,并用相同的代码处理它们。使用接口可获得与从抽象基类派生相同的优势;代码可以处理具有给定抽象级别的对象,并且可以定义具有新实现的新类,而无需修改现有代码,但这一点也可以通过抽象类来实现。我们可以说,一个类可以派生自单个抽象类,或者一个类可以实现多个接口。
为了验证,让我们创建一个场景来了解使用接口的真正好处是什么,以及接口优于接口的实际场景是什么。
概述
为相同的服务开发了两个库项目,一个使用抽象类开发,另一个使用抽象类和接口开发。简而言之,这只是一个演示项目,用于了解基于场景的抽象类和接口的使用。
解决方案包含两个库项目 **ProcessLibrary** 和 **ProcessAbstractLibrary**,它们都具有相同的文档导入和导出功能。Process library 包含一些额外的接口 **IProcess**、**IImportProcess** 和 **IExportprocess**,而 **ProcessAbstractLibrary** 中的所有工作都通过抽象类和其他类完成,而没有使用接口。其余的类在两个项目中都相同,没有额外的功能代码。
另一个类 **AprocessInfo** 通过 **ProcessAbstractLibrary** 中 **Aprocess** 实例的 DI(依赖注入)来实现,在 **ProcessLibrary** 中也做了同样的处理,**ProcessInfo** 类除了 **ProcessInfo** 类外,DI 通过 **IProcess** 实例实现。
**ClientProcessDisplay** 类有两个静态方法 **AbstractProcess()** 和 **InterfaceProcess()**,用于客户端 XML 文档的导入和导出过程。
AbstractProcess()
Console.WriteLine("***********Abstract Export Process**************", Environment.NewLine);
string str = "TEXTBTEXTC ";
//XMLExport exp = new XMLExport();
AXMLExport Aexp = new AXMLExport(str);
Aexp.FileNameTosave = "Test.XML";
Console.WriteLine(Aexp.ProcessType.ToString(), Environment.NewLine);
Console.WriteLine(Aexp.ExportType.ToString(), Environment.NewLine);
Console.WriteLine(Aexp.FilePath.ToString(), Environment.NewLine);
Console.WriteLine(Aexp.FileNameTosave.ToString(), Environment.NewLine);
Aexp.Save();
Console.WriteLine("***********Abstract Import Process**************", Environment.NewLine);
//XMLImport imp = new XMLImport();
AXMLImport Aimp = new AXMLImport("Test.XML");
Console.WriteLine(Aimp.ProcessType.ToString(), Environment.NewLine);
Console.WriteLine(Aimp.ImportType.ToString(), Environment.NewLine);
Console.WriteLine(Aimp.FileName.ToString(), Environment.NewLine);
Console.WriteLine(Aimp.Show(), Environment.NewLine);
Console.ReadKey();
InterfaceProcess()
Console.WriteLine("***********Export Process**************", Environment.NewLine); string str = "TEXTBTEXTC "; //XMLExport exp = new XMLExport(); XMLExport exp = new XMLExport(str); exp.FileNameTosave = "Test.XML"; Console.WriteLine(exp.ProcessType.ToString(), Environment.NewLine); Console.WriteLine(exp.ExportType.ToString(), Environment.NewLine); Console.WriteLine(exp.FilePath.ToString(), Environment.NewLine); Console.WriteLine(exp.FileNameTosave.ToString(), Environment.NewLine); exp.Save(); Console.WriteLine("***********Import Process**************", Environment.NewLine); //XMLImport imp = new XMLImport(); XMLImport imp = new XMLImport("Test.XML"); Console.WriteLine(imp.ProcessType.ToString(), Environment.NewLine); Console.WriteLine(imp.ImportType.ToString(), Environment.NewLine); Console.WriteLine(imp.FileName.ToString(), Environment.NewLine); Console.WriteLine(imp.Show(), Environment.NewLine); Console.ReadKey();
到目前为止一切正常,因为两者之间没有功能差异,但现在假设你想为现有的 ProcessAbstractLibrary 程序集代码创建单个派生类以进行导入和导出。
XMLOtherDocument :AExportImport , AImportProcess { //Implementation }
使用现有的 ProcessAbstractLibrary 代码无法做到这一点,你必须为此新类创建一个单独的代码,而不能使用现有的抽象类契约,否则你必须更改你的库代码。
让我们看看使用相同功能但通过接口契约开发的另一个库。
XMLOtherDocument: IExportProcess, IImportProcess { //Implementation }
这里你不需要对现有代码做任何更改,你可以毫无问题地使用现有契约。
所以现在我们可以说:
- 接口与作为基类的抽象类相同。
- 接口不能直接实例化。
- 接口支持事件、索引器、方法和属性。
- 接口包含公共方法,但没有任何实现。
- 接口是多重继承的替代方案。一个类可以继承自一个或多个接口。
- 接口提供松耦合,而具体类提供强耦合。
耦合: 简而言之,当一个类直接了解另一个类时,就称之为耦合。如果依赖类直接引用具体类,则称为强耦合;如果依赖类包含指向接口的指针,则称为松耦合。
部分接口: 接口可以与 partial 关键字一起使用,就像部分类一样,我们知道编译器会将部分元素组合起来以创建统一的接口定义。
partial interface IProcess { void DataProcess(); } partial interface IProcess { string ProcessType { get; } } class Process : IProcess { public void DataProcess() { //Logic } public string ProcessType { get { return "Data"; } } }
显式接口实现: 通过使用接口实现多重继承相当容易,直到成员相同。假设两个接口有相同的成员,如下所示:
interface IExportProcess { void Save(); } interface IImportProcess { void Save(); }
当 XMLDocument 对象向上转换为 IExportProcess 时,调用 Save 方法期望的结果与当对象向上转换为 IImportProcess 时调用相同方法的结果不同,这时就会出现问题。
要解决此问题,你可以使用显式接口实现,如下所示:
class XMLDocument :IExportProcess,IImportProcess { public void IExportProcess.Save() { //Logic } public void IImportProcess.Save() { //Logic } }
当一个使用显式接口实现的类的对象被向上转换为其中一个接口类型时,.Net 运行时会确保调用正确的成员实现。
在设计模式中有各种使用接口和抽象类的方法,如何处理它们取决于你。我认为这个小作品将帮助你理解抽象类和接口的用法。