CodeGym /课程 /C# SELF /多态的实际应用

多态的实际应用

C# SELF
第 21 级 , 课程 3
可用

1. 多态在实际中的用法

在编程里,多态就像一个万能遥控器:你按一下"音量+",电视、音响、空调都能响应——每个设备反应不一样,但界面是一样的!同理,不同类型的对象可以对同一个方法调用做出不同反应,只要这个方法在它们的共同父类里被定义成virtual

在C#里,多态的体现就是:基类(或者接口)类型的变量可以“装”下它的任何子类对象,然后你在这个变量上调用虚方法,实际执行的就是对象自己实现的那个方法。这是很多架构的基础,逻辑可以动态变化。

除了教材,这玩意儿有用吗?

有啊!几乎所有项目里,只要你要处理不同但类似的对象——比如动物园的动物、UI元素、事件处理器、文档流系统啥的——都离不开多态。

  • 能写出通用算法——代码只关心抽象,不用管具体实现细节。
  • 保证可扩展性——你可以加一百种新“动物”、“图形”、“处理器”,不用动老代码。
  • 减少程序各部分的耦合(这可是面试和架构里很重要的点!)。

2. 基本语法和机制

咱们来回顾并完善下我们的AnimalDogCat类,演示下多态怎么用。既然都说到“虚拟动物园”了,那就继续升级它吧。

带虚方法的基类


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 → Dog, Cat

当你调用animal.MakeSound(),而Animal变量里可能装着DogCat实例时,.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类型变量上调用的,但实际执行的是CircleRectangle的方法。现实里的图形编辑器、比如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 myDogAnimal 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();
}

但一般你这么干,说明架构设计有点问题(或者你滥用继承了)。

2
任务
C# SELF, 第 21 级, 课程 3
已锁定
实现基础多态和虚方法
实现基础多态和虚方法
2
任务
C# SELF, 第 21 级, 课程 3
已锁定
通用的多态对象处理函数
通用的多态对象处理函数
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION