home commit with new plugins

This commit is contained in:
2026-06-21 09:51:55 +08:00
parent ebb9e385c5
commit 3d067fe75c
85 changed files with 10794 additions and 1443 deletions
+424
View File
@@ -0,0 +1,424 @@
# 多态 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)
#多态
#核心