1. 多态在实际中的用法
在编程里,多态就像一个万能遥控器:你按一下"音量+",电视、音响、空调都能响应——每个设备反应不一样,但界面是一样的!同理,不同类型的对象可以对同一个方法调用做出不同反应,只要这个方法在它们的共同父类里被定义成virtual。
在C#里,多态的体现就是:基类(或者接口)类型的变量可以“装”下它的任何子类对象,然后你在这个变量上调用虚方法,实际执行的就是对象自己实现的那个方法。这是很多架构的基础,逻辑可以动态变化。
除了教材,这玩意儿有用吗?
有啊!几乎所有项目里,只要你要处理不同但类似的对象——比如动物园的动物、UI元素、事件处理器、文档流系统啥的——都离不开多态。
- 能写出通用算法——代码只关心抽象,不用管具体实现细节。
- 保证可扩展性——你可以加一百种新“动物”、“图形”、“处理器”,不用动老代码。
- 减少程序各部分的耦合(这可是面试和架构里很重要的点!)。
2. 基本语法和机制
咱们来回顾并完善下我们的Animal、Dog、Cat类,演示下多态怎么用。既然都说到“虚拟动物园”了,那就继续升级它吧。
带虚方法的基类
public class Animal
{
public string Name { get; set; }
public Animal(string name)
{
Name = name;
}
// 虚方法——可以被重写
public virtual void MakeSound()
{
Console.WriteLine($"{Name} 发出某种声音...");
}
}
public class Dog : Animal
{
public Dog(string name) : base(name) { }
public override void MakeSound()
{
Console.WriteLine($"{Name} 说:汪汪!");
}
}
public class Cat : Animal
{
public Cat(string name) : base(name) { }
public override void MakeSound()
{
Console.WriteLine($"{Name} 说:喵!");
}
}
多态的用法:动物集合的例子
假设你有一堆动物——有家养的也有野的——你想让它们都“叫一声”。没有多态你得写类型判断,代码又臭又长。有了多态,一行就搞定!
// 创建不同种类的动物数组
Animal[] animals = new Animal[]
{
new Dog("Buddy"),
new Cat("Molly"),
new Dog("Rover"),
new Cat("Whiskers"),
};
// 遍历数组,让每个动物叫一声
foreach (var animal in animals)
{
animal.MakeSound(); // 实际会调用Dog或Cat的实现,不是Animal的!
}
结果:
Buddy 说:汪汪!
Molly 说:喵!
Rover 说:汪汪!
Whiskers 说:喵!
这就是多态的魔力:一套通用代码,结果却根据对象的真实类型不同而不同。
图示:多态怎么工作的
Animal(基类)
/ \
Dog Cat
当你调用animal.MakeSound(),而Animal变量里可能装着Dog或Cat实例时,.NET运行时会自动决定到底该调用哪个MakeSound()。
3. 用多态解决常见问题
例子1:通用列表,不同操作
比如你写个游戏。有个基类GameObject,子类有敌人、队友、障碍物。它们都有Update()方法,但实现各不相同。
public class GameObject
{
public virtual void Update() { }
}
public class Enemy : GameObject
{
public override void Update()
{
Console.WriteLine("敌人进攻!");
}
}
public class Friend : GameObject
{
public override void Update()
{
Console.WriteLine("队友来帮忙!");
}
}
GameObject[] objects = new GameObject[]
{
new Enemy(),
new Friend(),
new Enemy()
};
foreach (var obj in objects)
{
obj.Update();
}
// 输出:
// 敌人进攻!
// 队友来帮忙!
// 敌人进攻!
例子2:把对象传给方法
你可以让方法接收基类类型参数,实际上传啥子类都行。这样以后加新子类也不用改方法,省心!
public static void FeedAnimal(Animal animal)
{
Console.Write($"{animal.Name}: ");
animal.MakeSound();
Console.WriteLine("并获得食物。");
}
FeedAnimal(new Dog("Rex"));
FeedAnimal(new Cat("Cleo"));
// 结果:
// Rex: Rex 说:汪汪!
// 并获得食物。
// Cleo: Cleo 说:喵!
// 并获得食物。
注意:没有多态你得判断类型、写一堆if else,手动调用不同方法,麻烦死了。
重点:运行时绑定
多态靠的是虚方法调用动态发生,也就是程序运行时才决定怎么走。这叫晚绑定(late binding)。即使变量类型是Animal,实际调用的还是对象自己的实现。
如果方法没加virtual,那调用的永远是变量类型里的实现(不是对象的)。所以想要灵活点,别省virtual!
4. 实战:扩展我们的应用
假设你想给虚拟动物园加个新功能:每个动物除了MakeSound(),还要能移动(Move())。但每种动物移动方式都不一样。
1. 在基类加虚方法
public class Animal
{
public string Name { get; set; }
public Animal(string name)
{
Name = name;
}
public virtual void MakeSound()
{
Console.WriteLine($"{Name} 发出某种声音...");
}
public virtual void Move()
{
Console.WriteLine($"{Name} 以未知方式移动...");
}
}
2. 子类里各自实现Move()
public class Dog : Animal
{
public Dog(string name) : base(name) { }
public override void MakeSound()
{
Console.WriteLine($"{Name} 说:汪汪!");
}
public override void Move()
{
Console.WriteLine($"{Name} 追着棍子跑。");
}
}
public class Cat : Animal
{
public Cat(string name) : base(name) { }
public override void MakeSound()
{
Console.WriteLine($"{Name} 说:喵!");
}
public override void Move()
{
Console.WriteLine($"{Name} 轻轻地溜达。");
}
}
3. 在集合里用两个方法
Animal[] animals = new Animal[]
{
new Dog("Beam"),
new Cat("Lucy")
};
foreach (var animal in animals)
{
animal.MakeSound();
animal.Move();
}
结果:
Beam 说:汪汪!
Beam 追着棍子跑。
Lucy 说:喵!
Lucy 轻轻地溜达。
在真实项目里,这种写法能让你写出很强大、可复用的模块。比如游戏里,所有对象管理器(NPC、特效啥的)都是这么搞的。
5. 实战题:画不同的图形
来个非动物园的例子,搞点图形。我们写个基类Shape,有虚方法Draw(),然后扩展它。
public class Shape
{
public virtual void Draw()
{
Console.WriteLine("画一个未知图形。");
}
}
public class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("画一个圆。");
}
}
public class Rectangle : Shape
{
public override void Draw()
{
Console.WriteLine("画一个矩形。");
}
}
// 图形集合
Shape[] shapes = new Shape[]
{
new Circle(),
new Rectangle(),
new Circle()
};
foreach (var shape in shapes)
{
shape.Draw();
}
这里Draw()是在Shape类型变量上调用的,但实际执行的是Circle或Rectangle的方法。现实里的图形编辑器、比如WinForms或WPF,就是这么设计的。
6. 通用套路:写通用算法
多态让你的代码变得灵活又可扩展。比如集合里不光能放Dog和Cat,还能放Hamster、Parrot、啥都行——你不用多写一行代码,就能用同样的方式处理它们。
而且你可以随便把子类对象传给接收基类参数的方法:
void PrintAnimalInfo(Animal animal)
{
Console.WriteLine($"名字:{animal.Name}");
animal.MakeSound();
animal.Move();
}
Animal hamster = new Animal("Hammy");
Animal dog = new Dog("Lord");
PrintAnimalInfo(hamster); // 用Animal的方法
PrintAnimalInfo(dog); // 用Dog的实现
7. 有用的小细节
常见问题和坑
很多新手觉得,创建Dog对象,然后用Dog myDog和Animal myDog没啥区别。其实如果你用Animal声明变量,它只能“看到”Animal里的东西(除了重写的方法),而用Dog声明就能看到所有Dog特有的东西,比如Bark()和专属属性。
还有一点:如果基类方法没加virtual,你在子类里不能用override重写。强行写编译器会报错。
对了,如果你真想“覆盖”一个非虚方法,可以用new关键字,但这属于进阶(而且容易出事)的用法。
为啥面试老爱问这个?
因为多态就像程序员的瑞士军刀:你会用它,就能写出可扩展、好维护的系统,代码不光能跑,还能活得久。比如让你实现多种支付方式(经典:BankCard、PayPal、Bitcoin),面试官就希望你写个通用接口(或抽象基类)有个Pay()方法,然后客户端能用Pay(BankCard)、Pay(PayPal)、Pay(Bitcoin),根本不用管里面怎么实现的。
8. 新手常犯的错误
最常见的错之一——想通过基类变量调用子类特有的方法。比如这样:
Animal animal = new Dog("Fido");
animal.Bark(); // 报错!Animal里没有Bark方法。
为什么不行?因为Animal类型的变量只能“看到”Animal里定义的东西,即使实际对象是Dog。你只能调用基类里有、并且被子类重写(override)的方法。
如果你真要用Dog特有的方法——只能强制类型转换:
Animal animal = new Dog("Fido");
if (animal is Dog dog)
{
dog.Bark();
}
但一般你这么干,说明架构设计有点问题(或者你滥用继承了)。
GO TO FULL VERSION