home commit with new plugins
This commit is contained in:
@@ -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)
|
||||
|
||||
#多态
|
||||
#核心
|
||||
Reference in New Issue
Block a user