VB Visual Basic 2026 中文教程
第 30 课 · 可替换、可扩展的对象设计

继承、接口与多态

上一课已经把应用拆成 Student、StudentService、StudentRepository 和 Form1。本课继续往更专业的面向对象设计迈进:使用继承表达“is-a”关系,用接口定义能力契约,再让 StudentService 依赖 IStudentRepository 而不是具体 StudentRepository。这样程序可以替换不同实现,而不必改动使用者代码,这就是多态的重要价值。

环境:Visual Studio 2026 · VB.NET · .NET 10重点:Inheritance · Interface · Polymorphism实作:StudentPolymorphismManager2026

本课学习目标

  • 理解继承(Inheritance)的基本用途。
  • 使用 Inherits 建立子类。
  • 认识 MustInheritMustOverride
  • 使用 Overrides 提供不同实现。
  • 理解接口(Interface)是能力契约。
  • 使用 Implements 实现接口。
  • 建立 IStudentRepository
  • StudentRepository 实现接口。
  • StudentService 依赖接口而不是具体类。
  • 理解多态如何允许替换不同 Repository 实现。
  • 完成 StudentPolymorphismManager2026。

30.1 三个概念先分清楚

继承、接口与多态经常一起出现,但它们解决的问题并不完全相同。

Inheritance

继承

让一个类继承另一个类已有的成员和行为。

Interface

接口

定义“必须提供哪些成员”,但不决定具体怎样做。

Polymorphism

多态

让相同的基类或接口变量可以指向不同具体实现。

30.2 什么是继承?

假设我们有一个一般的 Person:

Public Class Person

    Public Property Name As String

End Class

Student 是一种 Person,可以写:

Public Class Student
    Inherits Person

    Public Property StudentID As Integer
    Public Property Course As String
    Public Property Score As Decimal

End Class

Student 会拥有:

Name
StudentID
Course
Score
继承关系示意
PersonName
StudentStudentID · Course · Score
TeacherEmployeeID · Department

30.3 “is-a” 关系

继承适合表达:

Student is a Person.
Teacher is a Person.

但如果关系是:

Student has a Course.

通常更像组合(Composition),而不是继承。

不要为了复用几行代码就随便使用继承 继承应该表达合理的类型关系,而不是简单的“我想拿到另一个类的方法”。

30.4 MustInherit 抽象基类

某些类只应该作为父类使用,不应该直接 New:

Public MustInherit Class Person

    Public Property Name As String

    Public MustOverride Function GetRole() As String

End Class

MustInherit 表示这个类不能直接实例化。

30.5 MustOverride 与 Overrides

Student 必须提供 GetRole 的实现:

Public Class Student
    Inherits Person

    Public Property StudentID As Integer
    Public Property Course As String
    Public Property Score As Decimal

    Public Overrides Function GetRole() As String

        Return "Student"

    End Function

End Class

Teacher 可以提供不同实现:

Public Class Teacher
    Inherits Person

    Public Property EmployeeID As Integer
    Public Property Department As String

    Public Overrides Function GetRole() As String

        Return "Teacher"

    End Function

End Class

30.6 继承也能产生多态

Dim people As New List(Of Person) From {
    New Student With {
        .Name = "李明",
        .StudentID = 1,
        .Course = "Visual Basic 2026",
        .Score = 88D
    },
    New Teacher With {
        .Name = "王老师",
        .EmployeeID = 1001,
        .Department = "Computing"
    }
}

然后:

For Each person As Person In people

    txtOutput.Text &=
        $"{person.Name} - {person.GetRole()}" &
        Environment.NewLine

Next

虽然变量类型都是 Person,但运行时会自动调用真正对象自己的 GetRole。

30.7 什么是 Interface?

接口定义“一个类型必须提供哪些成员”。

Public Interface IStudentRepository

    Function GetAll() As List(Of Student)

    Function Insert(
        student As Student) As Integer

    Function Update(
        student As Student) As Integer

    Function Delete(
        studentId As Integer) As Integer

End Interface
接口不是 Class 它主要描述能力与契约。这里的 IStudentRepository 表示:“任何学生资料库实现,都必须能 GetAll、Insert、Update、Delete。”

30.8 Interface 与 Class 的区别

Class
可以保存状态、写完整方法实现,并建立对象。
Interface
主要定义成员契约,由实现它的类提供具体行为。
Inherits
表示继承另一个类。
Implements
表示实现一个接口。

30.9 StudentRepository 实现接口

Public Class StudentRepository
    Implements IStudentRepository

GetAll:

Public Function GetAll() As List(Of Student) _
    Implements IStudentRepository.GetAll

    ' 数据库读取代码

End Function

Insert:

Public Function Insert(
    student As Student) As Integer _
    Implements IStudentRepository.Insert

    ' INSERT SQL

End Function

Update 与 Delete 同样加入 Implements。

30.10 为什么 Repository 要使用接口?

上一课 StudentService 直接依赖:

StudentRepository

改成:

IStudentRepository

以后 StudentService 就不在乎实际对象是:

StudentRepository
InMemoryStudentRepository
TestStudentRepository
依赖具体类

StudentService 和 SQL Server Repository 绑得很紧,替换实现比较困难。

依赖接口

StudentService 只需要知道对方遵守 IStudentRepository 契约。

30.11 StudentService 改成依赖接口

Public Class StudentService

    Private ReadOnly repository As IStudentRepository

    Public Sub New(
        repository As IStudentRepository)

        Me.repository =
            repository

    End Sub

其他业务代码基本不用改变:

Public Function GetStudents() As List(Of Student)

    Return repository.GetAll()

End Function

30.12 多态真正发生在哪里?

看这一行:

Dim repository As IStudentRepository =
    New StudentRepository()

变量类型是:

IStudentRepository

实际对象是:

StudentRepository

这就是接口多态的核心。

30.13 建立第二个实现:InMemoryStudentRepository

为了清楚展示多态,我们再建立一个完全不连接数据库的实现:

Public Class InMemoryStudentRepository
    Implements IStudentRepository

    Private ReadOnly students As New List(Of Student)()

    Private nextId As Integer = 1

    Public Function GetAll() As List(Of Student) _
        Implements IStudentRepository.GetAll

        Return New List(Of Student)(
            students)

    End Function

Insert

Public Function Insert(
    student As Student) As Integer _
    Implements IStudentRepository.Insert

    student.StudentID =
        nextId

    nextId += 1

    students.Add(
        student)

    Return 1

End Function

Update

Public Function Update(
    student As Student) As Integer _
    Implements IStudentRepository.Update

    For Each existing As Student In students

        If existing.StudentID =
           student.StudentID Then

            existing.Name =
                student.Name

            existing.Course =
                student.Course

            existing.Score =
                student.Score

            Return 1

        End If

    Next

    Return 0

End Function

Delete

Public Function Delete(
    studentId As Integer) As Integer _
    Implements IStudentRepository.Delete

    For index As Integer =
        students.Count - 1 To 0 Step -1

        If students(index).StudentID =
           studentId Then

            students.RemoveAt(index)

            Return 1

        End If

    Next

    Return 0

End Function

End Class

30.14 不改 StudentService,就能替换实现

使用 SQL Server:

Dim repository As IStudentRepository =
    New StudentRepository()

Dim service As New StudentService(
    repository)

改成内存版:

Dim repository As IStudentRepository =
    New InMemoryStudentRepository()

Dim service As New StudentService(
    repository)

StudentService 不需要修改。

Form1调用 Service
StudentService只认识接口
IStudentRepository契约
SQL / Memory不同实现
同样 CRUD多态

30.15 实作:Student Polymorphism Manager 2026

建立项目:

StudentPolymorphismManager2026

建议结构:

StudentPolymorphismManager2026
├─ Models
│  ├─ Person.vb
│  ├─ Student.vb
│  └─ Teacher.vb
├─ Contracts
│  └─ IStudentRepository.vb
├─ Data
│  ├─ DatabaseHelper.vb
│  ├─ StudentRepository.vb
│  └─ InMemoryStudentRepository.vb
├─ Services
│  └─ StudentService.vb
└─ Form1.vb
Models
Contracts
Data
Services + UI

30.16 Form1 构造函数选择实现

Public Class Form1

    Private ReadOnly studentService As StudentService

    Public Sub New()

        InitializeComponent()

        Dim repository As IStudentRepository =
            New StudentRepository()

        studentService =
            New StudentService(
                repository)

    End Sub

如果只是为了测试,不想碰数据库:

Dim repository As IStudentRepository =
    New InMemoryStudentRepository()

其他 Form1 代码保持不变。

30.17 接口带来的实际好处

替换实现

SQL ↔ Memory

Service 不用改。

测试

不一定要连数据库

测试业务逻辑可以使用内存实现。

低耦合

依赖能力而非细节

Service 只要求 Repository 契约。

扩展

未来可加 API Repository

只要实现同一接口即可。

维护

职责更明确

数据库细节留在 Data 层。

多态

统一调用方式

相同接口调用不同对象行为。

30.18 继承还是接口?

使用继承

当类型之间真的存在稳定的“is-a”关系,并希望共享基础实现或统一基类行为。

使用接口

当你想定义“必须具备什么能力”,而不同类型可能用完全不同方式实现。

30.19 一个类可以实现多个接口

VB.NET 的类只能直接继承一个基类,但可以实现多个接口。

Public Class StudentRepository
    Implements IStudentRepository,
               IDisposable

这说明:

继承:一个主要基类
接口:可以多个能力契约
接口不是为了“绕过单继承限制”而存在 更重要的价值是定义能力边界,让调用方依赖抽象契约。

30.20 Overridable 与 Overrides

如果基类方法有默认实现,但允许子类改变:

Public Class Person

    Public Overridable Function GetDescription() As String

        Return Name

    End Function

End Class

Student:

Public Overrides Function GetDescription() As String

    Return
        $"{Name} - {Course} - {Score:N2}"

End Function

Overridable 表示“可以覆写”;MustOverride 表示“子类必须提供实现”。

30.21 基类变量与实际对象

Dim person As Person =
    New Student With {
        .Name = "李明",
        .StudentID = 1,
        .Course = "Visual Basic 2026",
        .Score = 88D
    }

person 能直接访问的是 Person 定义的成员,但运行时对象仍然是 Student。

如果真的需要确认具体类型:

If TypeOf person Is Student Then

    Dim student As Student =
        DirectCast(
            person,
            Student)

End If
不要为了使用子类功能而到处 DirectCast 如果代码经常需要判断具体类型,可能说明抽象设计还不够好。优先考虑把共同需要的行为放到基类或接口中。

30.22 初学者常见错误

错误 1

把继承当作代码复制工具

继承首先应该表达合理类型关系,而不只是为了拿到几个方法。

错误 2

接口里想放完整业务状态

接口主要定义契约;实际状态通常由实现类保存。

错误 3

Service 仍然 New StudentRepository

如果 Service 自己决定具体实现,就失去依赖接口的很多价值。

错误 4

以为多态等于方法重载

本课的多态重点是基类或接口引用在运行时调用不同具体实现。

30.23 本课完整对象协作流程

1
Form1 建立接口变量

变量类型是 IStudentRepository。

2
选择具体实现

StudentRepository 或 InMemoryStudentRepository。

3
注入 StudentService

Service 只依赖接口。

4
Service 调用 Repository

相同方法名,对应不同具体实现。

5
运行时决定行为

这就是多态。

30.24 小练习

  1. 建立 IReportExporter,定义 Export(students)
  2. 建立 CsvReportExporter 与 TextReportExporter,实现同一接口。
  3. 使用 IReportExporter 变量分别指向两个实现。
  4. 建立 Person → Student / Teacher 继承关系,并覆写 GetRole。
  5. 把 Person.GetDescription 改成 Overridable,再在 Student 中 Overrides。
  6. 在 StudentService 中确认没有直接 New StudentRepository。
  7. 把 Form1 的 Repository 切换成 InMemoryStudentRepository,确认 Service 不需要修改。

本课复习

  1. 继承、接口与多态分别解决什么问题?
  2. Inherits 的作用是什么?
  3. MustInherit 表示什么?
  4. MustOverrideOverrides 有什么关系?
  5. Interface 的主要作用是什么?
  6. Implements 用来做什么?
  7. 为什么 StudentService 应该依赖 IStudentRepository?
  8. 怎样用两个不同 Repository 实现展示多态?
  9. 继承和接口应该在什么情况下分别使用?
  10. 为什么频繁 DirectCast 可能代表设计需要改善?