VB.net学习笔记(十)继承



老人临终跟三个儿子说:“你们可以在我身上继承三样东西,金钱、信用卡和知识,你们可以选择。”
长子说:“我要金钱。”
次子说:“我要信用卡。”
三子说:“我要知识。”
老人说:“金钱在信用卡里,信用卡的密码在你们的弟弟那里,知道了密码才能取得钱财呀!知识就是金钱。”




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, "Short date")
            txtHireDate.Text = Format(.HireDate, "Short date")
            txtSalary.Text = Format(.Salary, "$0.00")
        End With
    End Sub
End Class


                 结果如下:

                   

        

          至此,我们再一次查看类图:

                          

               注意:1、同一类中几个方法的重载,可以省略Overloads。但如果一个方法写了Overloads,那些其余所有重载的都需加上。

                           2、不同类(父类与子类)之间的重载,必须加上Overloads。







7、重写

     重载是增加、扩展接口。

     重写而是把基类的功能改变或完全替代(Replace),又称覆盖,而不是保留现有功能。

      

      默认下基类未指明的成员都是不允许重写的。为了重写,必须用Overridable关键字来指明基类成员允许重写。

     (本质上是建立一个虚拟函数表,详查C++虚函数)

     例:在子类中改变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, value, 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, BirthDate, 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)多层次继承

               即子类成为另一个类的基类。

               使得上面上级类的成员全都继承到本级类中。得到极大的功能扩展。

              




'------------------------------------------------------------------------------------------------------------------------------------------ '本示例旨在向您展示如何在VB6下实现真正的继承,如何使用DyCOMHelper创建类,并使用,它将带您进入VB6背后的COM世界,看看我们的类是如何运行的。 'DyCOMHelper创建的对象的特点: '1、可以像VB6下定义的对象一样去使用,虽然不是VB6机制创建,但是可以被VB6当作对象来识别使用。 '2、支持后期绑定,支持错误处理,支持多接口等常规应用。 '3、支持真正意义上的继承,本示例将说明这一点。 '4、所创建的对象是轻量对象,实例占用的起步空间更小,约VB类的1/5。 '5、函数调用速度高出20% 左右 '6、实例创建的速度,最大可以提高近10倍 '7、释放速度,最大可以提高近100倍。(以上速度来自于极限测试数据) '8、创建模式比VB6更丰富,VB6的类实例,是在堆上进行创建的,效率很低,DyComHelper可以选择堆、堆栈是进行创建,也可以从某个结构上创建,也可以用内置的定长管理器进行创建。 '9、从类实例外部,访问内部数据时,支持真正意义的指针式访问。 '10、使用DyCOMHelper创建类,将让您的系统支持成千上万个类,而不会担心效率问题。 ' '读这个示例您可能会问的问题: ' 1、类型库,怎么来的?目前是我使用工具手动创建。未来,我将推出VB6插件,在VB6下直接书写代码生成类型库并自动引用。 ' '使用前,请先执行下列顺序: ' 1、引用 Types目录下 DyCOMHelperType.tlb,TestInheritLib.tlb。 ' 2、按F8开始逐步断点执行,看看它是如何工作的。 '示例中,有三个类,Animal、Wolf、Demiwolf,分别是动物、狼、狼狗,狼狗继承自狼,狼继承自动物,动物派生自IDispatch。 '------------------------------------------------------------------------------------------------------------------------------------------
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值