本课学习目标
- 理解继承(Inheritance)的基本用途。
- 使用 Inherits 建立子类。
- 认识 MustInherit 与 MustOverride。
- 使用 Overrides 提供不同实现。
- 理解接口(Interface)是能力契约。
- 使用 Implements 实现接口。
- 建立 IStudentRepository。
- 让 StudentRepository 实现接口。
- 让 StudentService 依赖接口而不是具体类。
- 理解多态如何允许替换不同 Repository 实现。
- 完成 StudentPolymorphismManager2026。
30.1 三个概念先分清楚
继承、接口与多态经常一起出现,但它们解决的问题并不完全相同。
继承
让一个类继承另一个类已有的成员和行为。
接口
定义“必须提供哪些成员”,但不决定具体怎样做。
多态
让相同的基类或接口变量可以指向不同具体实现。
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
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
30.8 Interface 与 Class 的区别
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 不需要修改。
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
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
30.22 初学者常见错误
把继承当作代码复制工具
继承首先应该表达合理类型关系,而不只是为了拿到几个方法。
接口里想放完整业务状态
接口主要定义契约;实际状态通常由实现类保存。
Service 仍然 New StudentRepository
如果 Service 自己决定具体实现,就失去依赖接口的很多价值。
以为多态等于方法重载
本课的多态重点是基类或接口引用在运行时调用不同具体实现。
30.23 本课完整对象协作流程
变量类型是 IStudentRepository。
StudentRepository 或 InMemoryStudentRepository。
Service 只依赖接口。
相同方法名,对应不同具体实现。
这就是多态。
30.24 小练习
- 建立 IReportExporter,定义 Export(students)。
- 建立 CsvReportExporter 与 TextReportExporter,实现同一接口。
- 使用 IReportExporter 变量分别指向两个实现。
- 建立 Person → Student / Teacher 继承关系,并覆写 GetRole。
- 把 Person.GetDescription 改成 Overridable,再在 Student 中 Overrides。
- 在 StudentService 中确认没有直接 New StudentRepository。
- 把 Form1 的 Repository 切换成 InMemoryStudentRepository,确认 Service 不需要修改。
本课复习
- 继承、接口与多态分别解决什么问题?
- Inherits 的作用是什么?
- MustInherit 表示什么?
- MustOverride 与 Overrides 有什么关系?
- Interface 的主要作用是什么?
- Implements 用来做什么?
- 为什么 StudentService 应该依赖 IStudentRepository?
- 怎样用两个不同 Repository 实现展示多态?
- 继承和接口应该在什么情况下分别使用?
- 为什么频繁 DirectCast 可能代表设计需要改善?