跳转至

面向对象编程

原文: https://zetcode.com/lang/visualbasic/oopi/

在 Visual Basic 教程的这一部分中,我们将讨论 Visual Basic 中的面向对象的编程。

那里有三种广泛使用的编程范例。 过程编程,函数式编程和面向对象的编程。 Visual Basic 支持过程式编程和面向对象的编程。

面向对象编程(OOP)是一种使用对象及其相互作用设计应用和计算机程序的编程范例。 (维基百科)

OOP 中有一些基本的编程概念:

  • 抽象
  • 多态
  • 封装
  • 继承

抽象通过建模适合该问题的类来简化复杂的现实。 多态是将运算符或函数以不同方式用于不同数据输入的过程。 封装对其他对象隐藏了类的实现细节。 继承是一种使用已经定义的类形成新类的方法。

对象

对象是 Visual Basic OOP 程序的基本构建块。 对象是数据和方法的组合。 在 OOP 程序中,我们创建对象。 这些对象通过方法进行通信。 每个对象都可以接收消息,发送消息和处理数据。

创建对象有两个步骤。 首先,我们创建一个类。 类是对象的模板。 这是一个蓝图,描述了类对象共享的状态和行为。 一个类可以用来创建许多对象。 在运行时从类创建的对象称为该特定类的实例。

Option Strict On

Module Example

    Class Being

    End Class

    Sub Main()

        Dim b as New Being
        Console.WriteLine(b.ToString())      

    End Sub

End Module

在第一个示例中,我们创建一个简单的对象。

Class Being

End Class

这是一个简单的类定义。 模板的主体为空。 它没有任何数据或方法。

Dim b as New Being

我们创建Being类的新实例。 为此,我们使用了New关键字。 b变量是创建对象的句柄。

Console.WriteLine(b.ToString())

对象的ToString()方法给出了该对象的一些基本描述。

$ ./object.exe 
Example+Being

由于类定义为空,因此我们没有太多信息。 我们获得对象类名称和模块名称,该对象的实例在此处创建。

对象属性

对象属性是捆绑在类实例中的数据。 对象属性称为实例变量或成员字段。 实例变量是在类中定义的变量,该类中的每个对象都有一个单独的副本。

Option Strict On

Module Example

    Class Person

        Public Name As String

    End Class

    Sub Main()

        Dim p1 as New Person
        p1.Name = "Jane"

        Dim p2 as New Person
        p2.Name = "Beky"

        Console.WriteLine(p1.Name)
        Console.WriteLine(p2.Name)      

    End Sub

End Module

在上面的 Visual Basic 代码中,我们有一个带有一个成员字段的Person类。

Class Person
    Public Name As String
End Class

我们声明一个Name成员字段。 Public关键字指定可以在Class End Class块之外访问成员字段。

Dim p1 as New Person
p1.Name = "Jane"

我们创建Person类的实例。 并将Name变量设置为"Jane"。 我们使用点运算符来访问对象的属性。

Dim p2 as New Person
p2.Name = "Beky"

我们创建Person类的另一个实例。 在这里,我们将变量设置为"Beky"

Console.WriteLine(p1.Name)
Console.WriteLine(p2.Name)

我们将变量的内容打印到控制台。

$ ./person.exe 
Jane
Beky

我们看到了程序的输出。 Person类的每个实例都有Name成员字段的单独副本。

方法

方法是在类主体中定义的函数/过程。 它们用于通过对象的属性执行操作。 在 OOP 范式的封装概念中,方法至关重要。 例如,我们的AccessDatabase类中可能有一个Connect方法。 我们无需通知Connect如何精确地连接到数据库。 我们只知道它用于连接数据库。 这对于划分编程中的职责至关重要,尤其是在大型应用中。

Option Strict On

Module Example

    Class Circle

        Public Radius As Integer

        Public Sub SetRadius(ByVal Radius As Integer)
            Me.Radius = Radius
        End Sub

        Public Function Area() As Double
            Return Me.Radius * Me.Radius * Math.PI
        End Function

    End Class

    Sub Main()

        Dim c As New Circle
        c.SetRadius(5)

        Console.WriteLine(c.Area())

    End Sub

End Module

在代码示例中,我们有一个Circle类。 我们定义了两种方法。

Public Radius As Integer

我们只有一个成员字段。 它是圆的RadiusPublic关键字是访问说明符。 它表明该变量可以从外界完全访问。

Public Sub SetRadius(ByVal Radius As Integer)
    Me.Radius = Radius
End Sub

这是SetRadius()方法。 这是一个正常的 Visual Basic 过程。 Me变量是一个特殊变量,我们用它来访问方法中的成员字段。

Public Function Area() As Double
    Return Me.Radius * Me.Radius * Math.PI
End Function

Area()方法返回圆的面积。 Math.PI是内置常数。

$ ./circle.exe 
78.5398163397448

运行示例。

访问修饰符

访问修饰符设置方法和成员字段的可见性。 Visual Basic 具有五个访问修饰符:PublicProtectedPrivateFriendProtectedFriendPublic成员可以从任何地方访问。 Protected成员只能在类本身内部以及继承的和父类访问。 可以从同一程序集(exe 或 DLL)中访问Friend成员。 ProtectedFriend是受保护的修饰符和朋友修饰符的组合。

访问修饰符可防止意外修改数据。 它们使程序更强大。

Option Strict On

Module Example

    Class Person

        Public Name As String
        Private Age As Byte

        Public Function GetAge() As Byte
            Return Me.Age
        End Function

        Public Sub SetAge(ByVal Age As Byte)
            Me.Age = Age
        End Sub

    End Class

    Sub Main()

        Dim p as New Person
        p.Name = "Jane"

        p.setAge(17)

        Console.WriteLine("{0} is {1} years old", _
           p.Name, p.GetAge)

    End Sub

End Module

在上面的程序中,我们有两个成员字段。 一个声明为Public,另一个声明为Private

Public Function GetAge() As Byte
    Return Me.Age
End Function

如果成员字段是Private,则访问它的唯一方法是通过方法。 如果要在类外部修改属性,则必须将方法声明为Public。 这是数据保护的重要方面。

Public Sub SetAge(ByVal Age As Byte)
    Me.Age = Age
End Sub

SetAge()方法使我们能够从类定义之外更改私有Age变量。

Dim p as New Person
p.Name = "Jane"

我们创建Person类的新实例。 因为Name属性是Public,所以我们可以直接访问它。 但是,不建议这样做。

p.setAge(17)

SetAge()方法修改Age成员字段。 由于已声明Private,因此无法直接访问或修改。

Console.WriteLine("{0} is {1} years old", _
    p.Name, p.GetAge)

最后,我们访问两个成员以构建一个字符串。

$ ./modifiers.exe 
Jane is 17 years old

Running the example.

Option Strict On

Module Example

    Class Base

        Public Name As String = "Base"
        Protected Id As Integer = 5323
        Private IsDefined As Boolean = True

    End Class

    Class Derived 
        Inherits Base

        Public Sub Info()
            Console.WriteLine("This is Derived Class")
            Console.WriteLine("Members inherited:")
            Console.WriteLine(Me.Name)
            Console.WriteLine(Me.Id)
            'Console.WriteLine(Me.IsDefined)
        End Sub

    End Class

    Sub Main()

        Dim drv As Derived = New Derived
        drv.Info()

    End Sub

End Module

在前面的程序中,我们有一个Derived类,该类继承自Base类。 Base类具有三个成员字段。 全部具有不同的访问修饰符。 IsDefined成员未继承。 Private修饰符可以防止这种情况。

Class Derived 
    Inherits Base

Derived继承自Base类。

Console.WriteLine(Me.Name)
Console.WriteLine(Me.Id)
'Console.WriteLine(Me.IsDefined)

PublicProtected成员由Derived类继承。 可以访问它们。 Private成员未继承。 访问成员字段的行被注释。 如果我们取消注释该行,它将无法编译。

$ ./protected.exe 
This is Derived Class
Members inherited:
Base
5323

运行程序,我们收到此输出。 PublicProtected成员是继承的,Private成员则不是。

方法重载

方法重载允许创建多个具有相同名称的方法,它们的输入类型彼此不同。

方法重载有什么好处? Qt4 库提供了一个很好的用法示例。 QPainter类具有三种绘制矩形的方法。 它们的名称为drawRect(),其参数不同。 一个引用一个浮点矩形对象,另一个引用一个整数矩形对象,最后一个引用四个参数,xywidthheight。 如果开发 Qt 的 C++ 语言没有方法重载,则库的创建者必须将其命名为drawRectRectF()drawRectRect()drawRectXYWH()之类的方法。 方法重载的解决方案更为优雅。

Option Strict On

Module Example

    Class Sum

        Public Function GetSum() As Integer
            Return 0
        End Function

        Public Function GetSum(ByVal x As Integer) As Integer
            Return x
        End Function

        Public Function GetSum(ByVal x As Integer, _
            ByVal y As Integer) As Integer
            Return x + y
        End Function

    End Class

    Sub Main()

        Dim s As Sum = New Sum

        Console.WriteLine(s.getSum())
        Console.WriteLine(s.getSum(20))
        Console.WriteLine(s.getSum(20, 30))

    End Sub

End Module

我们有三种方法GetSum()。 它们的输入参数不同。

Public Function GetSum(ByVal x As Integer) As Integer
    Return x
End Function

这一个参数。

Console.WriteLine(s.getSum())
Console.WriteLine(s.getSum(20))
Console.WriteLine(s.getSum(20, 30))

我们调用这三种方法。

$ ./overloading.exe 
0
20
50

这就是我们运行示例时得到的。

构造器

构造器是一种特殊的方法。 创建对象时会自动调用它。 构造器的目的是初始化对象的状态。 Visual Basic 中构造器的名称为New。 构造器是方法,因此它们也可以重载。

Option Strict On

Module Example

    Class Being

        Sub New()
            Console.WriteLine("Being is being created")
        End Sub

        Sub New(ByVal name As String)
            Console.WriteLine("Being {0} is created", name)
        End Sub

    End Class

    Sub Main()

        Dim b As New Being
        Dim t As New Being("Tom")

    End Sub

End Module

我们有一个Being类。 此类具有两个构造器。 第一个不带参数,第二个不带参数。

Sub New(ByVal name As String)
    Console.WriteLine("Being {0} is created", name)
End Sub

该构造器采用一个String参数。

Dim b As New Being

创建Being类的实例。 这次,在创建对象时调用没有参数的构造器。

$ ./constructor.exe 
Being is being created
Being Tom is created

这是程序的输出。

在下一个示例中,我们初始化类的数据成员。 变量的初始化是构造器的典型工作。

Option Strict On

Module Example

    Class MyFriend

        Private Born As Date
        Private Name As String

        Sub New(ByVal Name As String, ByVal Born As Date)
            Me.Name = Name
            Me.Born = Born
        End Sub

        Public Sub GetInfo()
            Console.WriteLine("{0} was born on {1}", _
                Me.Name, Me.Born.ToShortDateString)
        End Sub

    End Class

    Sub Main()

        Dim name As String = "Lenka"    
        Dim born As Date = #5/3/1990#

        Dim fr As MyFriend = New MyFriend(name, born)
        fr.GetInfo()

    End Sub

End Module

我们有一个带有数据成员和方法的Friend类。

Private Born As Date
Private Name As String

类定义中有两个变量。 Private关键字是访问修饰符。 它是一种封装。 Private关键字是限制性最强的修饰符。 它仅允许有问题的对象访问变量。 没有子孙,没有其他物件。

Sub New(ByVal Name As String, ByVal Born As Date)
    Me.Name = Name
    Me.Born = Born
End Sub

在构造器中,我们启动两个数据成员。 Me变量是用于引用对象变量的处理器。

Dim fr As MyFriend = New MyFriend(name, born)
fr.GetInfo()

我们创建带有两个参数的Friend对象。 然后,我们调用对象的GetInfo()方法。

./constructor2.exe 
Lenka was born on 5/3/1990

类常量

Visual Basic 可以创建类常量。 这些常量不属于具体对象。 他们属于类。 按照约定,常量用大写字母表示。

Option Strict On

Module Example

    Class Math

        Public Const PI As Double = 3.14159265359

    End Class

    Sub Main()
         Console.WriteLine(Math.PI)    
    End Sub

End Module

我们有一个带有PI常量的Math类。

Public Const PI As Double = 3.14159265359

Const关键字用于定义常数。

$ ./classconstant.exe 
3.14159265359

运行示例。

ToString()方法

每个对象都有一个ToString()方法。 它返回对象的人类可读表示。 默认实现返回对象类型的标准名称。 请注意,当我们使用对象作为参数调用Console.WriteLine()方法时,将调用ToString()

Option Strict On

Module Example

    Class Being

        Public Overrides Function ToString As String    
            Return "This is Being Class"
        End Function

    End Class

    Sub Main()

        Dim b as New Being
        Dim o As New Object

        Console.WriteLine(o.ToString())
        Console.WriteLine(b.ToString())     
        Console.WriteLine(b) 

    End Sub

End Module

我们有一个Being类,其中我们重写了ToString()方法的默认实现。

Public Overrides Function ToString As String    
    Return "This is Being Class"
End Function

创建的每个类都从基Object继承。 ToString()方法属于此Object类。 我们使用Overrides关键字来通知我们正在覆盖方法。

Dim b as New Being
Dim o As New Object

我们创建两个对象。 一种自定义定义,一种内置。

Console.WriteLine(o.ToString())
Console.WriteLine(b.ToString())  

我们在这两个对象上调用ToString()方法。

Console.WriteLine(b) 

正如我们之前指定的,在对象上调用Console.WriteLine()将调用其ToString()方法。

$ ./override.exe 
System.Object
This is Being Class
This is Being Class

这是我们运行脚本时得到的。

继承

继承是使用已经定义的类形成新类的方法。 新形成的类称为派生的类,我们派生的类称为基类。 继承的重要好处是代码重用和降低程序的复杂性。 派生类(后代)将覆盖或扩展基类(祖先)的功能。

Option Strict On

Module Example

    Class Being
        Sub New()
            Console.WriteLine("Being is created")
        End Sub
    End Class

    Class Human 
        Inherits Being

        Sub New()
            Console.WriteLine("Human is created")
        End Sub

    End Class

    Sub Main()

        Dim h As New Human

    End Sub

End Module

在此程序中,我们有两个类。 基类Being和派生的Human类。 派生类继承自基类。

Class Human 
    Inherits Being

在 Visual Basic 中,我们使用Inherits关键字创建继承关系。

Dim h As New Human

我们实例化派生的Human类。

$ ./inheritance.exe 
Being is created
Human is created

我们可以看到两个构造器都被调用了。 首先,调用基类的构造器,然后调用派生类的构造器。

接下来是一个更复杂的示例。

Option Strict On

Module Example

    Class Being

        Dim Shared Count As Integer = 0

        Sub New()
            Count = Count + 1
            Console.WriteLine("Being is created")
        End Sub

        Sub GetCount()
            Console.WriteLine("There are {0} Beings", Count)
        End Sub

    End Class

    Class Human 
        Inherits Being

        Sub New()
            Console.WriteLine("Human is created")
        End Sub

    End Class

    Class Animal 
        Inherits Being

        Sub New
            Console.WriteLine("Animal is created")
        End Sub

    End Class

    Class Dog
        Inherits Animal

        Sub New()
            Console.WriteLine("Dog is created")
        End Sub

    End Class

    Sub Main()

        Dim h As New Human
        Dim d As New Dog
        d.GetCount()

    End Sub

End Module

我们有四个类。 继承层次更加复杂。 HumanAnimal类继承自Being类。 Dog类直接继承自Animal类,并间接继承自Being类。 我们还介绍了Shared变量的概念。

Dim Shared Count As Integer = 0

我们定义一个Shared变量。 共享成员是类的所有实例共享的成员。 在其他编程语言中,它们称为静态成员。

Sub New()
    Count = Count + 1
    Console.WriteLine("Being is created")
End Sub

每次实例化Being类时,我们都会将Count变量加 1。 这样,我们就可以跟踪创建的实例数。

Class Animal 
    Inherits Being
...
Class Dog
    Inherits Animal
...

Animal继承自BeingDog继承自动物。 Dog也间接继承自Being

Dim h As New Human
Dim d As New Dog
d.GetCount

我们从HumanDog类创建实例。 我们称为Dog对象的GetCount()方法。

$ ./inheritance2.exe 
Being is created
Human is created
Being is created
Animal is created
Dog is created
There are 2 Beings

Human对象调用两个构造器:Dog对象调用三个构造器。 有两个实例化的存在。

抽象类和方法

抽象类无法实例化。 如果一个类至少包含一个抽象方法,则也必须将其声明为抽象方法。 抽象方法无法实现,它们仅声明方法的签名。 当我们从抽象类继承时,所有抽象方法都必须由派生类实现。 此外,必须以较少受限制的可见性声明这些方法。

与接口不同,抽象类可能具有完全实现的方法,并且可能具有定义的成员字段。 因此,抽象类可以提供部分实现。 程序员经常将一些通用功能放入抽象类中。 这些抽象类随后会被子类化以提供更具体的实现。 例如,Qt 图形库具有QAbstractButton,它是按钮小部件的抽象基类,提供按钮所共有的功能。 按钮Q3ButtonQCheckBoxQPushButtonQRadioButtonQToolButton都从此基本抽象类继承。

正式地说,抽象类用于强制执行协议。 协议是一组操作,所有实现对象都必须支持。

Option Strict On

Module Example

    MustInherit Class Drawing
        Protected x As Integer = 0
        Protected y As Integer = 0

        Public MustOverride Function Area() As Double

        Public Function GetCoordinates() As String
            Return String.Format("x: {0}, y: {1}", _
                Me.x, Me.y)
        End Function

    End Class

    Class Circle  
        Inherits Drawing

        Private Radius As Integer

        Sub New(ByVal x As Integer, ByVal y As Integer, _
            ByVal r As Integer)
            Me.x = x
            Me.y = y
            Me.Radius = r
        End Sub 

        Public Overrides Function Area() As Double
            Return Me.Radius * Me.Radius * Math.PI
        End Function

        Public Overrides Function ToString() As String
            Return String.Format("Circle, at x: {0}, y: {1}, radius: {2}", _
                Me.x, Me.y, Me.Radius)
        End Function

    End Class

    Sub Main()

        Dim c as New Circle(12, 45, 22)

        Console.WriteLine(c)
        Console.WriteLine("Area of circle: {0}", c.Area())
        Console.WriteLine(c.GetCoordinates())

    End Sub

End Module

我们有一个抽象基类Drawing。 该类定义两个成员字段,定义一个方法并声明一个方法。 一种方法是抽象的,另一种是完全实现的。 Drawing类是抽象的,因为我们无法绘制它。 我们可以画一个圆,一个点或一个正方形。 Drawing类对我们可以绘制的对象具有一些通用功能。

MustInherit Class Drawing

在 Visual Basic 中,我们使用MustInherit关键字定义抽象类。

Public MustOverride Function Area() As Double

抽象方法前面带有MustOverride关键字。

Class Circle  
    Inherits Drawing

圆是Drawing类的子类。 它必须实现抽象Area()方法。

$ ./abstractclass.exe 
Circle, at x: 12, y: 45, radius: 22
Area of circle: 1520.53084433746
x: 12, y: 45

程序的输出。

这是 Visual Basic 中 OOP 描述的第一部分。



回到顶部