浅谈继承
引言
面向对象的三大利器:封装,继承,多态,正是因为这三个主要特性,演化出了众多优秀的设计模式和框架,只有掌握了它们才能真正掌握面向对象。本文主要探讨继承的概念,都是些简单的语法,但是可能很多人在学习了若干框架、模式后,反而忘记或者说淡忘了一些基本的概念,笔者就是其中一个,故写下此文,一方面温故而知新,另一方面也强调基础的重要性。
概念
继承就是在类之间建立一种相交关系,使得新定义的派生类的实例可以继承已有的基类的特征和能力,而且可以加入新的特性或者是修改已有的特性建立起类的新层次。我们知道,现实中的事物都是有其相似和区别的,如果我们将相似的特征提取出来集中描述,不同的特征分别描述并包括那些相同的特征,那就是一个典型的继承。相似的特征我们可以构造一个基类封装起来,而对于那些有区别的特征,我们可以构建不同的子类封装并继承基类,从而扩展或重定义基类的行为。
基础语法
下面我想通过一些例子,探讨和总结继承的应用规则,示例代码如下:
场景一:
代码很简单,定义了一个基类BaseClass,包含一个无参构造函数,一个SayHello方法;子类ChildClass继承基类,但是不定义任何方法;Test类用于测试,创建一个ChildClass的实例,并调用SayHello方法,输出结果如下:
Base Class Constructor
Hello, everyone, I'm base class.
结论:在ChildClass中,我们没有定义任何的成员,只是继承BaseClass,但是却能调用父类的SayHello方法,并且从结果我们可以看出,整个的调用过程如下:第一步,实例化ChildClass,虽然我们没有定义它的构造函数,但是对于任何类,都有一个默认的构造函数,也就是说ChildClass中实际有这样一个构造函数:public ChildClass() { };通过输出结果,我们可以看出,ChildClass的构造函数实际调用了BaseClass的构造函数; 第二步,调用child实例的SayHello方法,很明显该方法是继承自BaseClass的。
场景二:
下面对ChildClass做一些小的修改,添加一个无参构造函数,添加一个SayHello方法,由于跟父类的方法重名了,故需要在SayHello前面加了个new关键字,用来表示隐藏基类的方法,不然编译器会报警,具体代码如下:
输出结果如下:
Base Class Constructor
Child Class Constructor
Hello, everyone, I'm child class.
结论:1 验证了前面说的,实例化的时候,会先调用父类构造函数,然后再调用子类构造函数;2 如果定义一个与父类重名的方法,会隐藏父类的方法而不会覆盖父类的方法,我们可以做个实验,修改Main方法如下:
实例化一个ChildClass后,将对象指向BaseClass的引用,输出结果如下:
Base Class Constructor
Child Class Constructor
Hello, everyone, I'm base class.
由此可见,如此调用的是基类的SayHello方法。
场景三:
修改BaseClass和ChildClass,添加关键字 virtual 和 override,具体代码如下:
将BaseClass的SayHello方法定义为虚方法,并在ChildClass中重写它,在Main方法中,实例化一个ChildClass对象,指向BaseClass的引用,并调用SayHello方法,输出结果如下:
Base Class Constructor
Child Class Constructor
Hello, everyone, I'm child class.
结论:如果用 virtual 和 ovrride关键字在父类中申明虚拟方法并在子类中重写,那么重写的方法将会覆盖父类的方法(注意对比前面用new关键字隐藏父类方法的例子),这样即便child变量指向的是BaseClass的引用实际调用的也是ChildClass的方法。
场景四:
在场景三的基础上修改BaseClass如下:
即修改BaseClass的构造函数为有参构造函数,这时候编译将会出错,提示说BaseClass不含有无参构造函数;有此我们可推断出以下几点:1 如果定义一个有参构造函数,将会覆盖默认的无参构造函数;2 如果子类含有无参构造函数,那么父类一定要含有无参构造函数,真的是这样吗?为了验证我们的想法,接着在BaseClass中添加一个无参构造函数,代码如下:
输出结果与场景三一致,这似乎验证了前面的猜测,那么试一下以下代码:
输出结果如下:
Base Class Constructor test
Child Class Constructor
Hello, everyone, I'm child class.
可以看出,虽然父类没有无参构造函数,但只要子类无参构造函数显示调用了父类的有参构造函数,同样可以完成子类的实例化,由此我们可以做出结论:子类实例化之前必须先实例化父类,既可以通过隐式调用父类无参构造函数(所谓隐式,就是指编译器自动完成),也可以通过显式调用父类构造函数完成。
总结
前面通过四个场景简单描述了继承的概念,主要有以下几点:
1 子类实例化的时候必须先调用父类的构造函数,默认会调用它的无参构造函数;
2 如果父类没有定义无参构造函数,子类构造函数必须显式地调用父类的有参构造函数;也就是说,子类实例化之前,必须先实例化父类。
3 如果子类出现了与父类同名的方法,并且没有用virtual override重写,那么将会隐藏而不会覆盖父类的方法;
4 如果子类用override关键字重写了基类的virtual方法,那么子类的方法将会覆盖基类的方法。