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