Files
2026-06-21 09:51:55 +08:00

425 lines
8.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 多态 vob
概念:
多态是面向对象三大特性之一
指的是:==同一个父类引用,指向不同子类对象时,调用同一个方法会表现出不同结果==
简单理解:
调用方只知道“这是一个动物”,但真正执行时,会根据对象实际是狗、猫还是鸟,执行各自的叫声
`vob` 可以这样记:
- `v``virtual`,父类声明虚方法
- `o``override`,子类重写父类虚方法
- `b``base`,子类中调用父类原本的成员
通常用法:
1. 父类提供统一入口
2. 子类根据自己的特点重写行为
3. 调用方只面向父类或接口写代码,不关心具体子类
4. 减少大量 `if/else` 类型判断
----
# 语法
父类:
```csharp
class 父类名
{
public virtual void 方法名()
{
}
}
```
子类:
```csharp
class 子类名 : 父类名
{
public override void 方法名()
{
}
}
```
调用:
```csharp
父类名 变量名 = new 子类名();
变量名.方法名();
```
----
# 基础案例
```csharp
class Animal
{
public virtual void Speak()
{
Console.WriteLine("动物发出声音");
}
}
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("狗:汪汪");
}
}
class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("猫:喵喵");
}
}
Animal a1 = new Dog();
Animal a2 = new Cat();
a1.Speak(); // 狗:汪汪
a2.Speak(); // 猫:喵喵
```
理解:
- `a1``a2` 的变量类型都是 `Animal`
- 但是它们实际指向的对象分别是 `Dog``Cat`
- 调用 `Speak()` 时,会执行实际对象对应的重写版本
这就是多态。
----
# 为什么要用多态
不用多态时,代码可能会这样:
```csharp
void MakeSpeak(Animal animal)
{
if (animal is Dog)
{
Console.WriteLine("狗:汪汪");
}
else if (animal is Cat)
{
Console.WriteLine("猫:喵喵");
}
}
```
问题:
1. 每增加一个新动物,就要改 `MakeSpeak`
2. 判断会越来越多
3. 行为分散在外部函数里,子类自己的特点没有放回子类中
使用多态后:
```csharp
void MakeSpeak(Animal animal)
{
animal.Speak();
}
```
以后新增子类:
```csharp
class Bird : Animal
{
public override void Speak()
{
Console.WriteLine("鸟:叽叽");
}
}
```
调用方不用改:
```csharp
MakeSpeak(new Bird());
```
这就是多态最重要的价值:
==把变化交给子类,把稳定入口留给父类==
----
# virtual
作用:
`virtual` 用在父类成员上,表示这个成员允许被子类重写
```csharp
class Character
{
public virtual void Attack()
{
Console.WriteLine("角色攻击");
}
}
```
注意:
1. 没有写 `virtual` 的普通方法,子类不能用 `override` 重写
2. `virtual` 常用于方法、属性、索引器、事件
3. 构造函数不能是 `virtual`
4. 静态方法不能被 `override`
----
# override
作用:
`override` 用在子类成员上,表示重写父类的虚成员
```csharp
class Warrior : Character
{
public override void Attack()
{
Console.WriteLine("战士挥剑攻击");
}
}
```
重写要求:
1. 父类成员必须是 `virtual``abstract` 或已经是 `override`
2. 子类方法名、参数列表、返回类型要匹配
3. 访问修饰符通常要保持一致,例如父类是 `public`,子类也要是 `public`
4. `override` 之后,这个方法仍然可以继续被更下一级子类重写,除非加 `sealed`
----
# base
作用:
`base` 用在子类内部,表示访问父类成员
常见用途:
1. 调用父类构造函数
2. 在重写方法中,先执行父类原本逻辑,再补充子类逻辑
```csharp
class Character
{
public virtual void Attack()
{
Console.WriteLine("消耗体力");
}
}
class Warrior : Character
{
public override void Attack()
{
base.Attack();
Console.WriteLine("战士挥剑攻击");
}
}
Warrior w = new Warrior();
w.Attack();
```
输出:
```text
消耗体力
战士挥剑攻击
```
理解:
`base.Attack()` 不是创建父类对象,而是在子类方法里调用父类原本的 `Attack`
----
# 多态调用看实际对象
多态最关键的一句:
==编译时看变量类型,运行时看实际对象==
```csharp
Animal animal = new Dog();
animal.Speak();
```
这里:
1. 编译时,`animal` 的类型是 `Animal`
2. 所以只能直接调用 `Animal` 中声明过的成员
3. 运行时,`animal` 实际指向的是 `Dog`
4. 所以 `Speak()` 执行的是 `Dog` 重写后的版本
示例:
```csharp
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("狗叫");
}
public void Bite()
{
Console.WriteLine("狗咬人");
}
}
Animal animal = new Dog();
animal.Speak(); // 可以,多态调用
// animal.Bite(); // 错误,Animal 类型里没有 Bite
```
如果确实要调用 `Dog` 独有方法,需要类型转换:
```csharp
Dog dog = animal as Dog;
if (dog != null)
{
dog.Bite();
}
```
但如果代码经常需要这样转,通常要回头检查抽象设计是不是不够好。
----
# override 和 new 的区别
`override` 是真正的多态重写。
```csharp
class Father
{
public virtual void Say()
{
Console.WriteLine("Father");
}
}
class Son : Father
{
public override void Say()
{
Console.WriteLine("Son");
}
}
Father f = new Son();
f.Say(); // Son
```
`new` 是隐藏父类成员,不是真正的多态。
```csharp
class Father
{
public void Say()
{
Console.WriteLine("Father");
}
}
class Son : Father
{
public new void Say()
{
Console.WriteLine("Son");
}
}
Father f = new Son();
f.Say(); // Father
```
区别:
1. `override`:看实际对象,所以会调用 `Son.Say()`
2. `new`:看变量类型,所以 `Father f` 调用的是 `Father.Say()`
3. 想要多态效果,用 `virtual + override`
----
# 常见错误
错误1:父类没写 `virtual`,子类却写 `override`
```csharp
class Animal
{
public void Speak()
{
}
}
class Dog : Animal
{
public override void Speak()
{
}
}
```
错误原因:
父类的 `Speak` 不是虚方法,不能被重写
----
错误2:以为子类独有成员也能通过父类变量直接调用
```csharp
Animal animal = new Dog();
// animal.Bite(); // 错误
```
原因:
父类变量只能看到父类声明过的成员
----
错误3:用 `new` 代替 `override`
```csharp
class Dog : Animal
{
public new void Speak()
{
Console.WriteLine("狗叫");
}
}
```
这样只是隐藏,不是真正的多态。
----
# 和里氏替换原则的关系
多态能成立的前提是:
==子类真的能当父类来用==
比如:
```csharp
void MakeSpeak(Animal animal)
{
animal.Speak();
}
```
这个函数相信:所有 `Animal` 都能 `Speak()`
如果某个子类重写后直接抛异常,或者完全破坏父类原本承诺的行为,就会影响[[里氏替换原则]]。
所以可以这样联系:
1. [[继承]]提供父子类型关系
2. 多态让同一个调用产生不同表现
3. [[里氏替换原则]]约束子类不要乱改父类承诺
----
# 注意
1. 多态主要依赖 `virtual + override`
2. 父类引用可以指向子类对象,这是多态的基础
3. 调用重写方法时,看的是对象的实际类型
4. 父类变量只能访问父类中声明过的成员
5. `base` 是在子类中访问父类成员,不是创建父类对象
6. `new` 是隐藏成员,不等于 `override`
7. 如果父类方法不希望再被重写,可以在子类重写时写 `sealed override`
----
# 一句话记忆
==父类定规则,子类改表现,调用时看实际对象。==
再压缩一点:
==virtual 允许改,override 真正改,base 调父类。==
----
# 面试/复习时怎么说
可以这样答:
“多态是指同一个父类引用指向不同子类对象时,调用同一个虚方法会执行不同子类的重写实现。
在 C# 中通常通过 `virtual``override` 实现,父类用 `virtual` 声明可重写方法,子类用 `override` 提供自己的实现。
如果子类重写时还想保留父类原本逻辑,可以用 `base` 调用父类方法。多态的好处是调用方可以面向父类编程,新增子类时尽量少改原有代码。”
----
引用:
1. Microsoft Learn: [Polymorphism](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/object-oriented/polymorphism)
2. Microsoft Learn: [virtual (C# Reference)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/virtual)
3. Microsoft Learn: [override (C# Reference)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/override)
4. Microsoft Learn: [The base keyword](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/base)
5. Microsoft Learn: [Knowing When to Use Override and New Keywords](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/knowing-when-to-use-override-and-new-keywords)
#多态
#核心