10.4 扩展类
我们仍然需要有某种方式创建新的元素对象。你已经看到“new Element”是不能用的,因为Element类是抽象的。因此,要实例化一个元素,需要创建一个扩展自Element的子类,并实现contents这个抽象方法。示例10.3给出了一种可能的做法:
示例10.3 定义ArrayElement作为Element的子类
ArrayElement类被定义为扩展(extend)自Element类。跟Java一样,可以在类名后面用extends子句来表达:
这样的extends子句有两个作用:它使得ArrayElement类从Element类继承(inherit)所有非私有的成员,并且它也让ArrayElement的类型成为Element类型的子类型(subtype)。由于ArrayElement扩展自Element,ArrayElement类被称作Element类的子类(subclass)。反过来讲,Element是ArrayElement的超类(superclass)。如果你去掉extends子句,Scala编译器会默认假定你的类扩展自scala.AnyRef,这对应到Java平台跟java.lang.Object相同。因此,Element类默认也扩展自AnyRef类。可以从图10.1中看到这些继承关系。
图10.1 ArrayElement的类图
继承(inheritance)的意思是超类的所有成员也是子类的成员,但是有两个例外:一是超类的私有成员并不会被子类继承;二是如果子类里已经实现了相同名称和参数的成员,那么该成员不会被继承。对后面这种情况我们也说子类的成员重写(override)了超类的成员。如果子类的成员是具体的而超类的成员是抽象的,我们也说这个具体的成员实现(implement)了那个抽象的成员。
例如,ArrayElement里的contents方法重写(或者说实现)了Element类的抽象方法contents。[2]与此不同的是,ArrayElement类从Element类继承了width和height这两个方法。例如,假定有一个ArrayElement ae,可以用ae.width来查询其宽度,就像width是定义在ArrayElement类一样:
子类型(subtying)的意思是子类的值可以被用在任何需要超类的值的场合。例如:
变量e的类型是Element,因此用于初始化它的值也应该是一个Element。事实上,初始值的类型是ArrayElement。这是可以的,因为ArrayElement类扩展自Element,这样,ArrayElement类型是与Element类型兼容的。[3]
图10.1还展示了ArrayElement和Array[String]之间存在的组合(composition)关系。这个关系被称为组合,因为ArrayElement用一个Array[String]组合出来,Scala编译器会在为ArrayElement生成的二进制类文件中放入一个指向传入的conts数组的字段。我们将在本章稍后的10.11节探讨关于组合和继承的设计考量点。