MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Visual Basic继承与多态性实现技巧

2022-03-185.0k 阅读

Visual Basic 继承的基础概念与实现

什么是继承

在面向对象编程中,继承是一种重要的概念。它允许一个类(称为子类或派生类)从另一个类(称为父类或基类)获取属性和方法。这就好比现实生活中子女继承父母的某些特征一样,子类会拥有父类的部分或全部特性,同时还可以在此基础上添加自己独特的属性和方法。

在 Visual Basic 中,继承使得代码的复用性大大提高。当我们创建一个新类时,如果它与现有的某个类有相似之处,我们无需从头开始编写所有的代码,而是可以让新类继承现有的类,然后根据需求进行扩展和修改。

继承的语法

在 Visual Basic 中,使用 Inherits 关键字来实现继承。假设我们有一个基类 Animal,代码如下:

Public Class Animal
    Public Property Name As String
    Public Sub Eat()
        Console.WriteLine(Name & " is eating.")
    End Sub
End Class

现在我们创建一个子类 Dog,它继承自 Animal 类,代码如下:

Public Class Dog
    Inherits Animal
    Public Sub Bark()
        Console.WriteLine(Name & " is barking.")
    End Sub
End Class

在上述代码中,Dog 类通过 Inherits Animal 语句继承了 Animal 类。这意味着 Dog 类自动拥有了 Animal 类的 Name 属性和 Eat 方法。同时,Dog 类还添加了自己特有的 Bark 方法。

访问修饰符与继承

  1. Public 修饰符:在基类中声明为 Public 的成员(属性、方法等),在子类中可以直接访问。例如,上述 Animal 类中的 Name 属性和 Eat 方法都是 Public 的,所以在 Dog 类中可以使用这些成员。
  2. Private 修饰符:声明为 Private 的成员只能在其所在的类内部访问。即使子类继承了基类,也无法直接访问基类中的 Private 成员。例如,如果我们将 Animal 类中的 Name 属性改为 Private
Public Class Animal
    Private Property Name As String
    Public Sub Eat()
        Console.WriteLine(Name & " is eating.")
    End Sub
End Class

此时,在 Dog 类中就无法直接访问 Name 属性了。如果尝试在 Dog 类的 Bark 方法中使用 Name 属性,会导致编译错误。 3. Protected 修饰符Protected 修饰的成员可以在其所在的类以及该类的子类中访问。如果我们将 Animal 类中的 Name 属性改为 Protected

Public Class Animal
    Protected Property Name As String
    Public Sub Eat()
        Console.WriteLine(Name & " is eating.")
    End Sub
End Class

那么在 Dog 类中就可以访问 Name 属性了。例如,我们可以在 Dog 类中添加如下代码:

Public Class Dog
    Inherits Animal
    Public Sub Bark()
        Console.WriteLine(Name & " is barking.")
    End Sub
End Class

这里 Dog 类可以访问 Animal 类中 Protected 修饰的 Name 属性。

  1. Friend 修饰符Friend 修饰的成员可以在同一程序集内的任何类中访问。如果一个类和它的基类在同一个程序集内,那么子类可以访问基类中 Friend 修饰的成员。例如:
Friend Class Animal
    Friend Property Name As String
    Public Sub Eat()
        Console.WriteLine(Name & " is eating.")
    End Sub
End Class

Public Class Dog
    Inherits Animal
    Public Sub Bark()
        Console.WriteLine(Name & " is barking.")
    End Sub
End Class

在上述代码中,Dog 类可以访问 Animal 类中 Friend 修饰的 Name 属性,因为它们在同一个程序集内(假设没有跨程序集引用等复杂情况)。

重写基类方法

重写的概念

当子类继承基类后,有时基类的某些方法在子类中有不同的实现逻辑。例如,在上述 AnimalDog 的例子中,Animal 类有一个 Eat 方法,可能不同的动物吃东西的方式略有不同,Dog 类可能需要以自己特有的方式实现 Eat 方法。这时就需要用到方法重写。

方法重写允许子类提供一个与基类中同名方法不同的实现。重写后的方法会在子类对象调用该方法时被执行,而不是执行基类中的原始方法。

重写的语法

  1. 在基类中定义可重写方法:要使基类中的方法能够被重写,需要在方法声明时使用 Overridable 关键字。例如,我们修改 Animal 类的 Eat 方法:
Public Class Animal
    Public Property Name As String
    Public Overridable Sub Eat()
        Console.WriteLine(Name & " is eating in a general way.")
    End Sub
End Class

这里 Eat 方法被声明为 Overridable,表示它可以被子类重写。 2. 在子类中重写方法:在子类中,使用 Overrides 关键字来重写基类的方法。例如,Dog 类重写 Eat 方法:

Public Class Dog
    Inherits Animal
    Public Overrides Sub Eat()
        Console.WriteLine(Name & " is eating dog food.")
    End Sub
    Public Sub Bark()
        Console.WriteLine(Name & " is barking.")
    End Sub
End Class

在上述代码中,Dog 类的 Eat 方法使用 Overrides 关键字重写了 Animal 类的 Eat 方法。当我们创建一个 Dog 对象并调用 Eat 方法时,会执行 Dog 类中重写后的 Eat 方法。示例代码如下:

Module Module1
    Sub Main()
        Dim myDog As New Dog()
        myDog.Name = "Buddy"
        myDog.Eat()
        myDog.Bark()
    End Sub
End Module

运行上述代码,输出结果为:

Buddy is eating dog food.
Buddy is barking.

可以看到,myDog.Eat() 调用的是 Dog 类中重写后的 Eat 方法。

调用基类的重写方法

有时候,在子类重写的方法中,我们可能还需要调用基类的原始方法。例如,Dog 类在重写 Eat 方法时,可能想在自己的逻辑前或后先执行基类 AnimalEat 方法逻辑。这时可以使用 MyBase 关键字。

修改 Dog 类的 Eat 方法如下:

Public Class Dog
    Inherits Animal
    Public Overrides Sub Eat()
        MyBase.Eat()
        Console.WriteLine(Name & " is eating dog food.")
    End Sub
    Public Sub Bark()
        Console.WriteLine(Name & " is barking.")
    End Sub
End Class

在上述代码中,MyBase.Eat() 调用了基类 AnimalEat 方法。再次运行 Main 方法,输出结果为:

Buddy is eating in a general way.
Buddy is eating dog food.
Buddy is barking.

可以看到,先执行了基类的 Eat 方法逻辑,再执行子类重写后的额外逻辑。

隐藏基类成员

隐藏的概念

在 Visual Basic 中,除了重写基类方法,子类还可以隐藏基类的成员。当子类声明了一个与基类中同名的成员时,就会隐藏基类的该成员。隐藏和重写是不同的概念,重写是对基类中可重写方法提供新的实现,而隐藏是在子类中创建一个新的成员来屏蔽基类中的同名成员。

隐藏的语法

  1. 在基类中定义成员:假设我们有一个基类 Shape,包含一个 Draw 方法:
Public Class Shape
    Public Sub Draw()
        Console.WriteLine("Drawing a shape.")
    End Sub
End Class
  1. 在子类中隐藏成员:现在创建一个子类 Circle,它隐藏了 Shape 类的 Draw 方法:
Public Class Circle
    Inherits Shape
    Public Shadows Sub Draw()
        Console.WriteLine("Drawing a circle.")
    End Sub
End Class

在上述代码中,Circle 类的 Draw 方法使用 Shadows 关键字隐藏了 Shape 类的 Draw 方法。当我们创建一个 Circle 对象并调用 Draw 方法时,会执行 Circle 类中隐藏后的 Draw 方法。示例代码如下:

Module Module1
    Sub Main()
        Dim myCircle As New Circle()
        myCircle.Draw()
    End Sub
End Module

运行上述代码,输出结果为:

Drawing a circle.

隐藏与重写的区别

  1. 重写要求基类方法必须是 Overridable:重写时,基类方法必须声明为 Overridable,子类使用 Overrides 关键字进行重写。重写后的方法在运行时会根据对象的实际类型来调用,体现了多态性。例如,如果我们有一个 Animal 类型的变量,实际指向一个 Dog 对象,调用 Eat 方法时会执行 Dog 类重写后的 Eat 方法。
  2. 隐藏不需要基类方法为 Overridable:隐藏时,基类方法不需要声明为 Overridable,子类使用 Shadows 关键字。隐藏后的成员在编译时根据变量的声明类型来决定调用哪个方法。例如,如果我们有一个 Shape 类型的变量,实际指向一个 Circle 对象,调用 Draw 方法时,若变量声明为 Shape 类型,则调用 Shape 类的 Draw 方法;若变量声明为 Circle 类型,则调用 Circle 类隐藏后的 Draw 方法。

Visual Basic 多态性的实现

多态性的概念

多态性是面向对象编程的重要特性之一,它允许我们使用一个基类类型的变量来引用不同子类类型的对象,并根据对象的实际类型调用相应的方法。简单来说,就是“同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果”。

在 Visual Basic 中,多态性主要通过继承和方法重写来实现。通过多态性,我们可以编写更通用、更灵活的代码,提高代码的可维护性和扩展性。

基于继承和重写的多态实现

我们继续以 AnimalDog 类为例。假设我们还有一个 Cat 类,它也继承自 Animal 类,并重写了 Eat 方法:

Public Class Cat
    Inherits Animal
    Public Overrides Sub Eat()
        Console.WriteLine(Name & " is eating cat food.")
    End Sub
End Class

现在我们可以使用多态性来编写如下代码:

Module Module1
    Sub Main()
        Dim animals() As Animal = {New Dog(), New Cat()}
        For Each animal In animals
            animal.Name = "Pet"
            animal.Eat()
        Next
    End Sub
End Module

在上述代码中,我们创建了一个 Animal 类型的数组,其中包含 DogCat 对象。通过遍历这个数组,我们调用每个对象的 Eat 方法。由于 DogCat 类都重写了 Animal 类的 Eat 方法,在运行时,会根据对象的实际类型(DogCat)来调用相应的 Eat 方法。运行结果如下:

Pet is eating dog food.
Pet is eating cat food.

这就是多态性的体现,同样的代码(animal.Eat())根据对象的实际类型执行了不同的逻辑。

接口与多态性

  1. 接口的定义:接口是一种特殊的抽象类型,它只定义方法的签名(名称、参数列表和返回类型),而不包含方法的实现。在 Visual Basic 中,使用 Interface 关键字来定义接口。例如,我们定义一个 IShape 接口:
Public Interface IShape
    Sub Draw()
End Interface
  1. 类实现接口:一个类可以通过 Implements 关键字来实现接口。例如,我们有 RectangleTriangle 类实现 IShape 接口:
Public Class Rectangle
    Implements IShape
    Public Sub Draw() Implements IShape.Draw
        Console.WriteLine("Drawing a rectangle.")
    End Sub
End Class

Public Class Triangle
    Implements IShape
    Public Sub Draw() Implements IShape.Draw
        Console.WriteLine("Drawing a triangle.")
    End Sub
End Class
  1. 基于接口的多态实现:我们可以使用接口类型的变量来引用实现该接口的不同类的对象,从而实现多态性。示例代码如下:
Module Module1
    Sub Main()
        Dim shapes() As IShape = {New Rectangle(), New Triangle()}
        For Each shape In shapes
            shape.Draw()
        Next
    End Sub
End Module

运行上述代码,输出结果为:

Drawing a rectangle.
Drawing a triangle.

这里通过接口实现了多态性,shape.Draw() 根据对象的实际类型(RectangleTriangle)调用了不同的 Draw 方法。

多态性在实际项目中的应用场景

图形绘制系统

在一个图形绘制系统中,可能有各种不同的图形,如圆形、矩形、三角形等。我们可以定义一个基类 Shape,并在其中定义一个 Draw 方法。每个具体的图形类(如 CircleRectangleTriangle)继承自 Shape 类,并重写 Draw 方法来实现自己的绘制逻辑。

然后,我们可以创建一个 Shape 类型的数组或集合,将不同的图形对象放入其中。在绘制图形时,通过遍历这个数组或集合,调用每个对象的 Draw 方法,就可以根据对象的实际类型绘制出不同的图形。这样的设计使得系统具有良好的扩展性,当需要添加新的图形时,只需要创建一个新的类继承自 Shape 类并实现 Draw 方法即可,而不需要修改大量现有的代码。

游戏角色行为系统

在游戏开发中,不同的角色可能有不同的行为,如攻击、防御、移动等。我们可以定义一个基类 Character,并在其中定义一些通用的方法,如 AttackDefendMove 等。然后,不同的角色类(如 WarriorMageThief)继承自 Character 类,并根据自身特点重写这些方法。

在游戏运行过程中,我们可以使用多态性来处理不同角色的行为。例如,当角色进行攻击时,通过一个 Character 类型的变量来引用不同的角色对象,调用 Attack 方法,就会根据角色的实际类型执行不同的攻击逻辑。这种设计使得游戏的角色行为易于管理和扩展,方便添加新的角色类型或修改现有角色的行为。

数据访问层

在企业级应用开发中,数据访问层可能需要访问不同类型的数据库,如 SQL Server、Oracle、MySQL 等。我们可以定义一个接口 IDataAccess,其中包含一些数据操作方法,如 GetDataInsertDataUpdateDataDeleteData 等。

然后,针对不同的数据库类型,创建具体的类(如 SqlServerDataAccessOracleDataAccessMySqlDataAccess)来实现 IDataAccess 接口,并在这些类中实现相应的数据库操作逻辑。在业务逻辑层,我们可以使用 IDataAccess 接口类型的变量来引用不同的数据库访问对象,根据配置或实际需求选择不同的数据库访问实现。这样,当需要更换数据库类型时,只需要在配置文件中修改相关设置,而不需要大量修改业务逻辑代码,提高了系统的可维护性和可移植性。

实现继承与多态性时的注意事项

合理使用访问修饰符

  1. 避免过度使用 Public:虽然 Public 修饰符使得成员可以在任何地方访问,但过度使用会破坏类的封装性。例如,在基类中,如果将一些内部实现细节的成员声明为 Public,子类可能会不恰当地使用这些成员,导致代码的耦合度增加,维护难度加大。
  2. 正确使用 Protected 和 Private:在设计基类时,要仔细考虑哪些成员应该是 Protected,哪些应该是 PrivateProtected 成员主要用于子类需要访问的一些基类内部逻辑相关的成员,而 Private 成员则用于完全封装在基类内部,不希望子类或其他外部类访问的成员。如果错误地将应该是 Private 的成员声明为 Protected,可能会导致子类意外地依赖这些内部实现,增加代码维护的风险。
  3. 注意 Friend 的作用范围:当使用 Friend 修饰符时,要清楚其作用范围是同一程序集内。如果项目涉及多个程序集,并且需要跨程序集访问某些成员,就要谨慎使用 Friend,可能需要考虑其他访问修饰符或通过接口等方式来实现跨程序集的交互。

重写方法的一致性

  1. 方法签名一致性:在重写基类方法时,子类重写方法的签名(方法名、参数列表和返回类型)必须与基类中被重写方法的签名完全一致。否则,编译器会将其视为一个新的方法,而不是重写方法,可能导致多态性无法正确实现。例如,如果基类 AnimalEat 方法没有参数,子类 Dog 在重写 Eat 方法时不能添加额外的参数,否则会出现编译错误。
  2. 语义一致性:除了方法签名要一致,重写方法的语义也应该与基类方法保持一致。也就是说,虽然重写方法的具体实现可能不同,但它应该完成与基类方法类似的功能。例如,Animal 类的 Eat 方法是关于动物进食的逻辑,Dog 类重写的 Eat 方法也应该围绕狗进食的相关逻辑,而不应该实现与进食无关的功能,否则会使代码的逻辑变得混乱,其他开发人员难以理解和维护。

避免多重继承带来的复杂性

  1. Visual Basic 不支持多重类继承:Visual Basic 语言本身不支持一个类从多个基类继承,这是为了避免多重继承带来的复杂性,如菱形继承问题(一个类从多个父类继承,这些父类又继承自同一个基类,导致继承结构呈现菱形,可能出现重复继承成员等问题)。虽然 Visual Basic 不支持多重类继承,但可以通过接口实现类似的功能。
  2. 谨慎使用多重接口实现:当一个类实现多个接口时,要注意接口成员的冲突问题。如果多个接口定义了同名的方法,实现类需要明确如何实现这些方法,以避免混淆和错误。同时,过多的接口实现可能会使类的职责变得不清晰,增加代码的复杂性。因此,在使用多重接口实现时,要确保每个接口的职责明确,类实现接口是合理且必要的。

性能考虑

  1. 虚方法调用的开销:在使用继承和多态性时,由于方法重写涉及虚方法调用(通过虚函数表来查找实际要调用的方法),相比于普通的方法调用,会有一定的性能开销。虽然现代的编译器和运行时环境对虚方法调用进行了优化,但在性能敏感的应用场景中,仍然需要考虑这种开销。例如,在一些对实时性要求极高的游戏开发或高性能计算场景中,如果频繁调用重写的虚方法,可能需要对代码进行性能分析和优化。
  2. 对象创建和内存管理:继承和多态性通常涉及到创建不同类型的对象。在创建对象时,会有内存分配和初始化的开销。同时,当对象不再使用时,需要进行内存回收。如果在程序中频繁创建和销毁大量基于继承关系的对象,可能会对内存管理和性能产生影响。因此,在设计时要考虑对象的生命周期管理,尽量减少不必要的对象创建和销毁操作,以提高程序的整体性能。

通过深入理解和正确应用 Visual Basic 中的继承与多态性,结合实际项目需求,注意上述提到的各种要点,开发人员可以编写出更健壮、灵活和高效的代码,提升软件系统的质量和可维护性。无论是小型应用程序还是大型企业级项目,继承与多态性都是构建良好架构的重要基石。同时,不断地在实践中总结经验,优化代码,能够更好地发挥这些面向对象特性的优势,应对日益复杂的软件开发挑战。