接口和抽象类
本章介绍了如何创建接口和抽象类(现在不考虑其成员,第 10 章会讲述类的成员)。这两种类型在许多方面都十分类似,所以应看一下它们的相似和不同之处,看看哪些情况应使用什么技术。
首先讨论它们的类似之处。抽象类和接口都包含可以由派生类继承的成员。接口和抽象类都不能直接实例化,但可以声明这些类型的变量。如果这样做,就可以使用多态性把继承这两种类型的对象指定给它们的变量。接着通过这些变量来使用这些类型的成员,但不能直接访问派生对象的其他成员。
下面分析它们之间的区别。派生类只能继承一个基类,即只能直接继承一个抽象类(但可以用一个继承链包含多个抽象类)。相反,类可以使用任意多个接口。但这不会产生太大的区别—这两种情况取得的效果是类似的。只是采用接口的方式稍有不同。
抽象类可以拥有抽象成员(没有代码体,且必须在派生类中实现,否则派生类本身必须也是抽象的)和非抽象成员(它们拥有代码体,也可以是虚拟的,这样就可以在派生类中重写)。另一方面,接口成员必须都在使用接口的类上实现—它们没有代码体。另外,按照定义,接口成员是公共的(因为它们的目的是在外部使用),但抽象类的成员可以是私有的(只要它们不是抽象的)、受保护的、内部的或受保护的内部成员(其中受保护的内部成员只能在应用程序或派生类中访问)。此外,接口不能包含字段、构造函数、析构函数、静态成员或常量。
抽象类主要用作对象系列的基类,这些对象共享某些主要特性,例如,共同的目的和结构。接口则主要用于类,这些类存在根本性的区别,但仍可以完成某些相同的任务。
例如,假定有一个对象系列表示火车,基类 Train
包含火车的核心定义,例如车轮的规格和引擎的类型(可以是蒸汽发动机、柴油发动机等)。但这个类是抽象的,因为并没有 “一般的” 火车。为创建一辆实际的火车,需要给该火车添加特性。为此,派生一些类,例如 PassengerTrain
、FreightTrain
和 424DoubleBogey
等,如图 9-11 所示
。
也可以用相同的方式来定义汽车对象系列,使用 Car
抽象基类,其派生类有 Compact
、SUV
和 PickUp
。Car
和 Train
甚至可以派生于一个相同的基类 Vehicle
,如图 9-12 所示
。
现在,层次结构中的一些类共享相同的特性,这是因为它们的目的是相同的,而不只是因为它们派生于同一个基类。例如,PassenagerTrain
、Compact
、SUV
和 Pickup
都可以运送乘客,所以它们都拥有 IPassenagerCarrier
接口。FreightTrain
和 Pickup
可以运送重载货物,所以它们都拥有 IHeavyLoadCarrier
接口,如图 9-13 所示
。
在进行更详细的分解之前,把对象系统以这种方式进行分解,可以清晰地看到哪种情形适合使用抽象类,哪种情形适合使用接口。只使用接口或只使用抽象继承,就得不到这个示例结果。