老人临终跟三个儿子说:“你们可以在我身上继承三样东西,金钱、信用卡和知识,你们可以选择。”
长子说:“我要金钱。”
次子说:“我要信用卡。”
三子说:“我要知识。”
老人说:“金钱在信用卡里,信用卡的密码在你们的弟弟那里,知道了密码才能取得钱财呀!知识就是金钱。”
1、为什么有继承?
类B全部或部分有类A的代码,如果用复制粘贴法,会加大代码量,且使可读性降低。
其中的A就被称为父类、基类或超类,而B被称为子类
2、继承的本质含义?
继承正如财产继承一样,将一个类的所有成员全部继承到另一个类,例如,B类继承于A类。
那么,A类的所有成员(数据、方法、属性等)都是B类的元素,因为它继承了嘛。
但是,继承是一回事,能不能直接访问这些成员又是另一回事,不要被后面什么不能访问到父类中的私有所迷惑。
因为继承所有后,访问控制是一个重点。
3、继承过来的所有成员的困扰
在类B中不仅有继承过来的A类成员,还有B类自己的成员,这样一个大家庭,将相互影响,上演一场好戏。
比如: 使得B类将A类某成员功能加强、功能显现几个,或者重写,或者隐藏。
在访问控制、功能加减中,常说父类中的某某某,而不说成在B类中由A类继承过来的某某某。
4、创建基类
有父才有子。如果没有明确指明类不能做为基类,那它可以派生子类。
加入类:一、项目->添加类。 (推荐,这样以单独文件存在,便于重用)
二、直接用关键字Class来添加。
Public Class Person Public Property Name() As String Public Property BirthDate() As Date End Class
创建后可在解决方案中,右击->查看类图,可以看到UML图展示类的成员
5、创建子类
同4创建一个类,为了表示这是某某类的子类,在类中加入关键字Inherits 。
下例中表示继承自Person这个类
Public Class Employee Inherits Person Public Property HireDate() As Date Public Property Salary() As Double End Class
如果使用Inherits继承当前项目外部的类时,而要指定包括该类的名称空间,或者在类的顶端用Imports语句来引入引用的名称空间。
上例表明Employee类包括了Person类的所有功能和接口。
所以我们可以这样使用:
Public Class Form1 Private Sub btnOK_Click(sender As Object,e As EventArgs) Handles btnOK.Click Dim emp As New Employee With emp .Name = "Fred" .BirthDate = #1/1/1960# .HireDate = #1/1/1980# .Salary = 30000 txtName.Text = .Name txtBirthDate.Text = Format(.BirthDate,"Short date") txtHireDate.Text = Format(.HireDate,"Short date") txtSalary.Text = Format(.Salary,"$0.00") End With End Sub End Class
尽管Employee中看不出来有成员Name、BirthDate,但它的确继承来自Person,所以它有这样的成员,故而能用。
6、子类中的重载
尽管Employee继承后有name,Birthdate方法,但我们仍可定义自己的Name、BirthDate方法。这种叫重载。
重载基类中的现有方法在本质上与重载常规方法相同(即:同名、不同参)
不同在于,重载基类成员必须加上OverLoads
Public Enum NameType '枚举 informal = 1 formal = 2 End Enum Public Class Employee Inherits Person Public Property HireDate() As Date Public Property Salary() As Double Public mNames As New Generic.Dictionary(Of NameType,String) '泛型,存储键/值的集合 Public Overloads Property Name(ByVal type As NameType) As String '必须的overlaods指明和Person的Name是重载 Get '否则将遮蔽Person中的Name Return mNames(type) End Get Set(value As String) If mNames.Contains(type) Then mNames.Item(type) = value Else mNames.Add(type,value) End If End Set End Property End Class
注意:1、在子类中如果同名且同参,将发生隐藏(shadows),例得基类相同方法“消失”。后面再说。
2、NameType是枚举型,mNames是用来存储建/值的集合对象,类似于数组,通过键来索引存储查找值。
Public Class Form1 Private Sub btnOK_Click(sender As Object,e As EventArgs) Handles btnOK.Click Dim emp As New Employee() With emp .Name = "Fred" .Name(NameType.formal) = "Mr.Frederick" '新加,正式名,重载 .Name(NameType.informal) = "Freddy" '新加,非正式名,重载 .BirthDate = #1/1/1960# .HireDate = #1/1/1980# .Salary = 30000 txtName.Text = .Name txtFormal.Text = .Name(NameType.formal) '重载 txtInformal.Text = .Name(NameType.informal) '重载 txtBirthDate.Text = Format(.BirthDate,"$0.00") End With End Sub End Class
结果如下:
至此,我们再一次查看类图:
注意:1、同一类中几个方法的重载,可以省略Overloads。但如果一个方法写了Overloads,那些其余所有重载的都需加上。
2、不同类(父类与子类)之间的重载,必须加上Overloads。
7、重写
重载是增加、扩展接口。
重写而是把基类的功能改变或完全替代(Replace),又称覆盖,而不是保留现有功能。
默认下基类未指明的成员都是不允许重写的。为了重写,必须用Overridable关键字来指明基类成员允许重写。
例:在子类中改变BirthDate功能(校验是否满16岁),先在基类中指明BirthDate可以被子类改写(重写)
Public Class Person Public Overridable Property BirthDate() As Date '仅表示它可被重写 Public Property Name() As String End Class
再在子类中加入下面数据及属性:
用Overrides来说明子类中用本方法来重写:
Private mBirthDate As Date Public Overridable Property BirthDate() As Date '说明要重写 Get Return mBirthDate End Get Set(value As Date) If DateDiff(DateInterval.Year,value,Now) > 16 Then mBirthDate = value Else Throw New AggregateException("at least 16") '新构错误信息对象 End If End Set End Property注:上面新构一个错误信息对象,当满足条件时,将抛出一个异常消息。
8、MyBase
由于重写后,基类的有此方法无法再次进行引用。比如重写后,无法引用基类的同名方法。
此时用MyBase来强制引用来自基类的方法。不管是否重载或重载,它会一步到位无差错使用基类的成员。
Public Overrides Property BirthDate() As Date Get Return MyBase.BirthDate End Get Set(value As Date) If DateDiff(DateInterval.Year,Now) >= 16 Then MyBase.BirthDate = value Else Throw New AggregateException("at least 16") End If End Set End Property
9、虚拟方法
虚拟方法是指能够由子类重写和替换的方法,如上面,重写BirthDate是一个虚拟方法的例子。
以下是根据C++个人猜想:
每个变量有自己的内存地址,使得程序能准确地查找变量的值。
同样,每个方法(函数)块也有自己的内存起始地址,使得程序也能准确地调用方法。
对象中的方法,将依据其内存中的方法地址进行调用,使得准确地使用对应的方法。
一般情况下,子类有自己的方法地址,基类有自己的方法地址,这样对应查找调用。
但是,
在重写情况下,方法地址列表中会增加一个结点,指向一个虚拟函数表。
一旦用了Overridabel,那将建立一个虚函数表,会将Overridable的所有成一个列表进行罗列。
一旦用了Overrides,虚函数表中就会替换原来的方法地址。调用时就调用对应的方法。
可以详查这里进行了解:http://blog.csdn.net/haoel/article/details/1948051/
于是这样虚拟方法给我们一个多样化的状态,变量类型不是决定因素,而是实际引用对象类型才是最重要的。
下例:变量类型Person,引用对象类型Person,不会发生歧义:
Public Class Form1 Private Sub btnOK_Click(sender As Object,e As EventArgs) Handles btnOK.Click Dim emp As New Person() With emp 'emp既是person的变量也是person的引用(两者相同不会产生歧义) .Name = "Fred" .BirthDate = #1/1/2000# txtName.Text = .Name txtBirthDate.Text = Format(.BirthDate,"Short date") End With End Sub End Class
下例:变量类型是Employee,引用对象类型是Employee,也不会发生歧义:
Private Sub btnOK_Click(sender As Object,e As EventArgs) Handles btnOK.Click Dim emp As Person emp = New Employee() 'emp是person类型,但引用的是Employee With emp '所以最终使用的employee,下面就是用的employee .Name = "Fred" .BirthDate = #1/1/2000# '故这里会到employee中去验证 txtName.Text = .Name txtBirthDate.Text = Format(.BirthDate,"Short date") End With End Sub
下例::变量类型是Person,引用对象类型是Employee,发生重写。
Public Class Form1 Private Sub btnOK_Click(sender As Object,e As EventArgs) Handles btnOK.Click Dim emp As Person emp = New Employee() With emp .Name = "Fred" .BirthDate = #1/1/2000# End With DisplayPerson(emp) '尽管是person类型,便实际上传入的emp,故在下面调用中会校验BirthDate End Sub Private Sub DisplayPerson(ByVal thePerson As Person) With thePerson txtName.Text = .Name txtBirthDate.Text = Format(.BirthDate,"Short Date") End With End Sub End Class
由此得出两个重要规则:
1、基类类型的变量总是可以保存其子类对象的引用。
2、虚拟方法中,对象的数据类型才是最重要的,变量的数据类型并不是决定的因素。
10、重写已重载的方法
重写是来改写或代替原功能。
有时我们需要同时进行,即重写也重载。重写是对基类而言,重载是对子类而言,针对的方向不同,
就形成了怪异的“重写已重载的方法”这个概念。
如下:子类中对基类中Name()先是重载,但它又想改写基类的Name()方法。
于是先用Overrides来改写基类中的Name,改写后,发现子类中已经有了Name(NameType),于是得在其前再加OverLoads,它表明这个
Name与子类中带参的Name是重载的关系。
Public Class person Private mName As String Public Overridable Property Name() As String Get Return mName End Get Set(value As String) mName = value End Set End Property End Class Public Enum NameType informal = 1 formal = 2 normal = 3 End Enum Public Class customer Inherits person Private mNames As New Generic.Dictionary(Of NameType,String) Public Overloads Property Name(ByVal type As NameType) As String Get Return mNames(type) End Get Set(value As String) If mNames.ContainsKey(type) Then mNames.Item(type) = value Else mNames.Add(type,value) End If If type = NameType.normal Then MyBase.Name = value End If End Set End Property Public Overloads Overrides Property Name() As String Get Return mNames(NameType.normal) End Get Set(value As String) mNames(NameType.normal) = value End Set End Property End Class
Note that you are using both the Overrides keyword (to indicate that you are overriding the Name method from the base class)
and the Overloadskeyword (to indicate that you are overloading this method in the subclass).
注意:使用的Overrides关键字表明是重写基类(Person)中的Name()方法
而 Overloads 关键字则表明重载的是子类(Employee)的Name()方法,因为前面已经有了方法签名Name(NameType)。
为了兼容,上例中枚举增加了normal,同时在带参Name中增加对基类的写入:
Public Overloads Property Name(ByVal type As NameType) As String Get Return mNames(type) End Get Set(value As String) If mNames.ContainsKey(type) Then '此处更新Employ中的值 mNames.Item(type) = value Else mNames.Add(type,value) End If If type = NameType.normal Then '此处更新Person中的值。这样同时更新了两处 MyBase.Name = value End If End Set End Property
11、隐藏(Shadows)
其实,隐藏应该先于重写了解。
隐藏就如同作用域一样,全局变量在过程中被局部同名变量隐藏。
如果基类设计有误而又无法得到源码,或者基类适用大多情况,但有特殊情况时又得改写。
由于基类中方法设计时就是不允许重写(没有Overridable),这里想在子类中“改写”这个方法,怎么办?
用Shadows可以隐藏基类的同名方法。
简单地说:
重写是征得允许(Overridable)后的改写。
隐藏则是未经允许强行强行改写基类的方法。
当声明一个方法,如果不是用Overrides关键字,它就是非虚拟方法,而非虚拟方法是不能被子类重写和替代的方法。
而隐藏就是这样,它要重写非虚拟方法,不管它们是否声明时使用了Overridable,它无视规则。
所以,隐藏比重写更血腥,如果说重写是有法有依的司法人员,那么隐藏就是无法无天的抢劫犯。
因此隐藏打破了规则,基类开发人员在把方法标记或不标记Overridable时总是很小心,确保方法在子类是否重写,以
保证基类能够继续正常使用。常常没有标记Overridable的不希望重写,而Shadows却打破了这个规则。
正常时,期望 子类对象Employee不仅是Employee对象,而且还是Person对象(因为Employee是Person的子类),
然而,使用Shadows根本上改变了Employee的功能,便它不再是Person对象,这种本质上背离正常期望的方法会
看例子,先在Person中定义非虚拟方法---只读Age,使其故意为负,以便在子类中改正:
Public Class Person Public Overridable Property BirthDate() As Date Public Overridable Property Name() As String Public ReadOnly Property Age() As Integer Get Return CInt(DateDiff(DateInterval.Year,Now,BirthDate)) '故意使年龄为负 End Get End Property End Class
在子类Employee中隐藏基类的错误做法:
Public Shadows ReadOnly Property Age() As Integer 'shadows可省,省略会出现警告 Get '只要同名函数就会发生隐藏 Return CInt(DateDiff(DateInterval.Year,BirthDate,Now)) End Get End Property
注意的是Shadows是可省略的,但IDE会警告,最好加上。
隐藏发生在方法签名相同的情况下。
然后我们看几个调用情况,来说明与重写的不同。
重写主要看的是实际使用对象的类型。
而隐藏却是相反,它只看变量的初始类型。
下面变量类型为Person,故用的是Person中Age,所以为负:
下面变量类型是Employee,所以Age被隐藏,使用的是子类Employee中的,即为正值:
下面变量类型是Person,故使用的是基类的Age,故为负值:
shadows是一个怪兽,它可以改变原来的只读为可读写:
Public Shadows Property Age() As Integer '将只读改成可读写 Get Return CInt(DateDiff(DateInterval.Year,Now)) End Get Set(ByVal value As Integer) BirthDate = DateAdd(DateInterval.Year,-value,Now) End Set End Property
这样使得使用时发生意想不到的问题,下面变量类型Person,所以Age只能为只读,所以提示出错:
改变一下变量类型为Employee:
如果更为极端时,Shadows这个怪兽还可以把方法变成实例变量、或将属性变成方法。
比如在子类Employee中把Age变成下面:
Public Shadows Age As String
这都说明了Shadows在隐藏基类元素时,子类会改变其牲或作用域。
说到作用域,隐藏实际就是作用情况:
而在基类和子类中,原理同样:
变量的数据类型说明的作用域的范围,而实际的对象数据类型,只是表明其值。所以隐藏如同作用域一样发生。
在上面规则下,隐藏总是发生在“最短路径上”,比如在子类域中,应该是访问的是子类,但由于子类中的该元素是Private无法访问则将向上找一个最近的作用域。如下:
上面根据变量类型划分了三个作用域,
first是最大,能够调用自身,所以为firstClass
Second其次,本身隐藏了上面,要使用本类的display,可惜的是为Private,无法访问,于是,向上找最短路径为firstclass,故显示它。
third最小,分别隐藏上面的first和second,且能调用本身,故显示是thirdClass。(如果这里也设置为private,将最终调用的是first)
Public Class Form1 Private Sub Button1_Click(sender As Object,e As EventArgs) Handles Button1.Click Dim a As New useClasses a.showZ() End Sub End Class Public Class baseCls Public z As Integer = 100 '声明要被隐藏的 End Class Public Class dervCls Inherits baseCls Public Shadows z As String = "*" '激活隐藏 End Class Public Class useClasses Dim basObj As baseCls = New dervCls() Dim derObj As dervCls = New dervCls() Public Sub showZ() MsgBox("Accessed through base class: " & basObj.z) '基类变量,故100 MsgBox("Accessed through derived class: " & derObj.z) '子类变量,故 * End Sub End Class
12、继承的层次
(1)多继承
即一个子类同时继承两个或以上的基类。
.net Framework不支持多继承,因此VB.net也不支持它。
但是,可以使用多接口来获得与多继承相似的功能。
(2)多层次继承
即子类成为另一个类的基类。
使得上面上级类的成员全都继承到本级类中。得到极大的功能扩展。
原文链接:https://www.f2er.com/vb/258636.html