关于继承----父与子的纠葛
继承表明一种具体化和一般化的过程。像狗和猫是哺乳动物的具体化,而哺乳动物是狗和猫的一般化。这样就构成了一棵继承树,向下具体化,向上一般化。
名字纷争:
“基类”和“派生类”是比较多的用来形容继承树中上下节点的词语,但同样多的说法是父类和子类。那么,父与子是一般化与具体化的关系么?父亲是儿子的一般化,儿子是父亲的具体化?显然不能这么理解,那为什么人们如此的习惯用父与子来联系OO中的继承呢?
继承内涵丰富,人们用“具体化和一般化”把这个抽象的东西解释出来,但是“具体化和一般化”也还有些抽象,于是再进一步说成“父与子”,可能离原本的意思会远一点,但这样理解起来更容易。而且继承中派生类会拥有基类中的一部分信息,这和人类社会中子承父业又是何曾相似啊。。。
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
上面写出了一个简单的继承,父类拥有私有的名firstName,受保护的姓lastName,公开的方法EarnMoney()和虚方法Marry().而子类有私有的名firstName,继承自父类的姓lastName,隐藏了父亲的EarnMoney()方法,重写了Marry()方法。以下是两个类的伪类图
内存分配:
为什么如此画类图呢,因为看起来像内存分配一样。这部分的关键在堆栈的分配以及引用类型的访问界限问题(别忘了引用类型是类型安全的,即访问内存时是受控的)。
Father personFather = new Father();
personFather只是一个引用类型,它是被分配到栈上的,而当new后,会在堆上创建出一个实在的Father类实例,并让personFather指向这个Father类的实例,同时限定访问的界限。同理换成
Son personSon = new Son();
也是一样的道理。而此时personSon.EarnMoney()时使用的就是Son的方法了。因为Son的此方法把Father的EarnMoney方法隐藏掉了。
但继承的强大不止是代码的重用,还有任何子类都是一个父类并表现为多态。
Father person = new Son();
如上定义时,内存分配如何呢?
person是一个Father类的引用,指向了Son的实例。但是可访问的部分只是Son继承自Father的部分而已,Son自我扩展的部分引用变量person是没有权限访问的!那么为什么person.Marry()时使用的是Son的方法?这不表示person访问了它不应该能访问的地方么?这就要说说override了,因为Marry()虽然在person的访问范围之外,但被标记为override后,person就有权利去查询override表,找到它就可以使用了;相似的,为什么person.EarnMoney()只能访问Father的方法而无法访问Son的呢。。。上文说过了,因为引用变量person没有那个权限并且override表中查不到EarnMoney().
内存的分配搞懂了才可以知道父子之间的隐藏,重写是怎样的一种恩怨情仇。而针对抽象编程,用到最多就是这种多态啦。
构造函数:
大家都知道父类是abstract时,是不能被实例话的,但是抽象父类却可以有自己的构造函数。为什么抽象类不能被实例化?为什么不能被实例化还要有构造函数?
众所周之,抽象类的主要意图是给派生类声明一个规约,所以很多方法只有声明却根本没有实现,(当然也有的抽象类里面根本没有抽象的东西,但那不是抽象类的意图)那么对于这样的不完全类我们怎么来实例化呢?根本不应该实例化!就像在研制火箭的时候试验某一个部分的特性会制造一个模型来仿真,这个部分是真实的,但其他部分是假的,我们能把这样的模型火箭发射出去么?显然不应该考虑发射的问题。
那为什么还要有构造函数?因为抽象类里毕竟还是有些实现的,这些字段或者资源是需要初始化的。
在父类子类之间的继承中,构造函数是不被遗传的,所以子类总要显式或者隐式的调用父类的构造函数。用父类的构造函数初始化继承自父类的部分,用自己的构造函数初始化扩展的部分,各司其职。
相互转化:
前文说过,继承的强大是表现在多态性,也就是说每一个子类实例既是子类类型也是父类类型。类型的多样就是多态。
这样就可以利用多态性来降低程序的耦合增加程序的灵活性,所以程序段中的使用或者很多方法的参数都倾向于使用父类引用,而实际的指向或者实参却是子类的实例。对于前一种说了很多,下面说一下参数传递中的父与子。
这样想提一下委托,System命名空间下有一个预定义委托EventHandler,它的定义是:
[ComVisibleAttribute(true)]
public delegate void EventHandler(Object sender, EventArgs e)
这个方法中的两个参数都是基类的,一个是万类之父,一个是事件数据之父。两个基类占着位置,但是传入的实参基本不会是这两个父类的实例,而是它们子类的实例。这样相当于在使用时做了如下处理:
object sender = sonOfObject; //sonOfObject 是一个object子类的实例
EventArgs e = sonOfEventArgs; //sonOfEventArgs 是一个EventArgs子类的实例
如此的话,只能访问子类中的override部分啦。致使子类的访问受到限制。那么我们就要显示的转化一下,让子类的实例名副其实的变成子类。
SonOfObject son = sonOfObject as SonOfObject;
这样就可以访问我们在子类扩展的全部代码啦。
by 千冰念@YITIAN Studio
程序员的路是一行一行走出来的,不管多困难,我能做的只是,绝不低头。