继承
继承是面向对象中扩展对象的一种方法。子类通过继承父类,以获得父类的属性和方法。
我们通常称子类也为派生类,而称父类为基类。
下面的代码中,Button类就是继承关系中的基类,ImageButton类派生自Button类,于是,Button就是ImageButton类的父类(基类)。ImageButton就是Button类的一个派生类。
class Button
{
string text = "";
Rect rect = null;
//...
}
//C++; class ImageButton : public Button
//C#; class ImageButton : Button
//Java; class ImageButton extends Button
class ImageButton : Button
{
Image image = new Image();
//...
}
有旧,有新,有借,有蓝;在一只鞋里放一枚六便士的银币。
----维多利亚时代的谚语
我们强调有旧,有新,也就说,为什么要继承呢?在上面的代码中我使用了C#的语法,而忽略了一些语法细节,但是Java的语法更好的说明了继承的作用,就是其目的在于扩展。
这里,ImageButton不但有了父类中的属性,比如text,也有了Button的区域,并且有了新的成员,Image对象。这是Button对象所没有的。
当然了,这并不是全部,继承也会继承父类的方法(我们暂且不去理会基类中的private方法)。
class Button
{
private String text = "";
private Rect rect = null;
public void setText(String text) {
this.text = text;
}
}
class ImageButton extends Button
{
private Image image = null;
public void setImage(Image image) {}
}
/
ImageButton button = new ImageButton();
button.setText("Cancel");
button.setImage(new Image());
button实例,不但可以访问父类的方法setText(...),设置按钮的文本,还可以调用ImageButton类自己的方法setImage()去设置该按钮的图片。
我们还有许多派生的例子,比如说:
class CDialog
{
}
class MyDialog : public CDialog
{
}
这样,我就创建了一个自己的对话框类。
IS-A
讲述一个最重要的对象关系,is-a,用自然语言表达就是说“是一个”,“是一种”。它表达了一种正确的派生关系。
Button button = new ImageButton();
或者,把代码写成如下的形式:
ImageButton imageButton = new ImageButton();
Button button = (Button)imageButton;
这份代码,表明了这样一点,一个ImageButton对象,也是一个Button对象。这种关系就像是说猫是一种动物,飞机是一种交通工具一样。这就是我们所说的is-a关系。
An ImageButton is-a Button, also.
这个关系非常重要,它将直接影响到设计,和代码的可理解性。因为继承给了我们如此简单的扩展方式,于是,滥用继承也成为了一个问题。
class B extends A
{
}
我们要思考继承的合理性,语法上成立的代码,未必是合理的代码,代码上显示B对象也是A对象。但是我们要用自然语言去解读一下,一个B对象is-a A对象嘛?这很自然嘛?很合理嘛?很通顺嘛?
当然了,也不必那么完全符合我们对自然界的认识。有很多成功的继承,虽然看起来很怪异,但是它确实是行之有效的扩展。
多继承
比如说现在的许多手机,它既是一个移动电话,也是一个iPod。那么很显然,它确实具有两个合理的is-a关系。
而,C++支持多继承,正是因为事物可能具有多种性质(这个性质并非属性的意思,你可以参考波粒二象性的深意)。
class iPhone : public MobilePhone, public iPod
{
}
我很高兴C++支持多继承,也很高兴Apple给了这样一个合理的例子,让我觉得我没有错误地使用多继承(其实,我相信还是有很多人会站出来说这个继承关系不恰当。也许吧,但是它确实可以工作,这个很重要。)。
【1】事实上,在C++中,往往多继承,更多体现于一种技巧的使用。在后面,我们会提及这些技巧。
继承还是聚合?
C++支持多继承,而绝大多数的面向对象语言不支持这种特性,于是通常采用聚合的方式来实现对象的扩展,这是一个非常好的习惯。
class iPhone extends MobilePhone
{
private iPod o = new iPod();
}
然而,聚合不能取代继承。
聚合引发的对象关系是has-a,而继承(C++中特指public继承)引发的对象关系是is-a。从设计的角度来讲,这种差别很大,尽管我们可以用聚合实现继承,但是在我们需要is-a关系的时候,聚合就不那么方便了。
但是,我们这样写,是被鼓励的。而且对于Java和C#来说,这样似乎是唯一的做法(其实是有其他的做法的,但是那已经不是语法层次上面可以解决的问题了。【2】)。
多继承还是单继承?
尽管C++支持多继承,我们也不能滥用多继承,因为软件工程证明了,大多数的多继承是有害的,尽管许多C++的类库使用了多继承的方式,但是如果你没有想清楚,就不要设计出这样的继承关系。尽量用聚合来代替继承关系。
【2】. 我们会在后面讨论的。那是一个模式的使用问题。