CodeGym /课程 /C# SELF /多态的概念及其在OOP中的作用

多态的概念及其在OOP中的作用

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

1. 入门

想象下你家里有电视、音响、空调和智能灯泡。每个都有自己的遥控器。你要开电视,就拿电视遥控器按"开机"。要开音响,就拿音响遥控器按同样的"开机"。想象一下?是不是有点乱套?

那如果你有一个万能遥控器呢?你拿着它,按"开机",它就能神奇地知道你现在要开哪个设备,然后发出正确的指令。而且遥控器自己根本不知道电视怎么开,也不知道音响怎么开。它只知道这些设备都有个“开机功能”。

这就是多态在编程里的类比!

多态

“多态”这个词来自希腊语poly(多)和morph(形态)。字面意思就是“多种形态”。在OOP里,它指的是对象能有多种形态,或者说,同一个方法在不同对象上能有不同表现。

在C#里,多态主要靠继承和virtualoverride方法搞定。

多态的核心思想就是向上转型(upcasting)。啥意思?

回到我们的AnimalDogCat这套体系。我们知道,狗动物,猫动物。这种“is-a”关系就是继承的基础。


public class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("某种声音...");
    }
}

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("汪汪!");
    }
}

public class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("喵!");
    }
}

因为有“is-a”关系,我们可以把子类对象赋值给父类变量。这就叫向上转型(upcasting),因为我们把对象“提升”到更通用的类型。C#编译器会自动帮你搞定:


// 我们有一只具体的狗
Dog myDog = new Dog();

// 可以把Dog对象赋值给Animal类型的变量!
// 这就是向上转型(upcasting)。
// 右边是具体的Dog,左边是更通用的Animal。
Animal generalAnimal = myDog;

// 现在可以调用MakeSound()!
generalAnimal.MakeSound(); // 输出"汪汪!"(不是"某种声音...",而是"汪汪!")               

发生了啥?

  1. Animal generalAnimal = myDog;时,我们没创建新动物,只是把myDog(其实是Dog)放进了标着Animal的“盒子”里。
  2. 编译阶段(代码变成字节码时),变量generalAnimal类型是Animal。所以编译器只让你用Animal类里有的方法和属性。
  3. 但有意思的是:到运行时(runtime),你调用generalAnimal.MakeSound(),.NET运行环境看的是对象的实际类型Dog),不是变量类型(Animal)!因为MakeSound()Animal里是virtual,在Dog里是override,所以会调用Dog的实现。

这种“方法的行为由运行时对象的实际类型决定,而不是变量类型”,就叫动态分派或者运行时多态

想象一个盒子:外面写着“水果”(就是Animal变量类型)。你把苹果放进去(就是Dog对象)。你说“水果,发出声音!”(调用MakeSound()),盒子知道里面是苹果,就发出“咔嚓”,而不是啥通用的“水果声”。

2. 多态实战演示

想真正理解多态,最好的办法就是看它怎么用,尤其是你有一堆对象的时候。

我们来扩展下“我的小动物园”应用。假设你在宠物医院上班,各种动物都来体检。你希望每种动物体检流程不一样,但调用体检的代码能统一写。

先在Animal基类和它的DogCat子类里加个新的虚方法Examine()


public class Animal
{
    public string Name { get; set; }
    public Animal(string name) { Name = name; }
    public virtual void MakeSound() { Console.WriteLine($"{Name} 发出某种声音..."); }
    public virtual void Examine()
    {
        Console.WriteLine($"体检 {Name}:");
        Console.WriteLine("  - 检查呼吸。");
        Console.WriteLine("  - 测量体温。");
    }
}

public class Dog : Animal
{
    public Dog(string name) : base(name) { }
    public override void MakeSound() { Console.WriteLine($"{Name} 说: 汪!"); }
    public override void Examine()
    {
        base.Examine();
        Console.WriteLine("  - 检查牙齿。");
        Console.WriteLine("  - 狂犬疫苗。");
    }
}

public class Cat : Animal
{
    public Cat(string name) : base(name) { }
    public override void MakeSound() { Console.WriteLine($"{Name} 说: 喵!"); }
    public override void Examine()
    {
        base.Examine();
        Console.WriteLine("  - 检查爪子。");
        Console.WriteLine("  - 驱虫药。");
    }
}

现在我们有了狗和猫专属的Examine()方法。注意重写方法里的base.Examine();,这样可以先执行基类Animal的通用体检,再加上每种动物自己的检查。这很方便!

现在假设我们的宠物医院有一批动物来体检:


class Program
{
    static void Main()
    {
        Animal[] animals = {
            new Dog("雷克斯"),
            new Cat("咪咪"),
            new Animal("兔子")
        };

        Console.WriteLine("--- 动物体检 ---");
        foreach (Animal animal in animals)
        {
            animal.Examine();
            Console.WriteLine();
        }

        Console.WriteLine("--- 声音时间 ---");
        foreach (Animal animal in animals)
            animal.MakeSound();
    }
}

结果:


--- 动物体检 ---
体检 雷克斯:
  - 检查呼吸。
  - 测量体温。
  - 检查牙齿。
  - 狂犬疫苗。

体检 咪咪:
  - 检查呼吸。
  - 测量体温。
  - 检查爪子。
  - 驱虫药。

体检 兔子:
  - 检查呼吸。
  - 测量体温。

--- 声音时间 ---
雷克斯 说: 汪!
咪咪 说: 喵!
兔子 发出某种声音...

这就是多态的威力! 我们只写了一个foreach (Animal animal in animals)循环,每次都调用同一个animal.Examine()(或animal.MakeSound())。但每次都会执行正确的、专属的实现——狗的、猫的、还是通用动物的。代码简单、干净、通用,具体细节都藏在各自的类里。

明天要是来了只仓鼠Hamster,你只要写个Hamster : Animal类,重写Examine(),它就能无缝加入体检系统,foreach循环一行都不用改!这就是多态的真正力量。

3. 多态在OOP中的作用

多态不是个花哨词,也不是代码小把戏。它是让你程序灵活、可扩展、好维护的根基。来看看它到底有啥用:

灵活性和可扩展性(Open/Closed Principle)

这大概是最重要的优点。多态让你写的代码对扩展开放,对修改封闭。啥意思?

  • 对扩展开放: 你可以加新动物类型(比如BirdFishHamster),只要继承Animal并重写需要的方法就行。
  • 对修改封闭: 你不用改任何用Animal[]的现有代码。那个foreach循环调用animal.Examine(),无论你加多少新动物都不用动。

想想如果没有多态,你得写多少if-else ifswitch,每加一种动物都得改一堆代码,头都大了。

简化代码

多态让你处理各种相关但不同类型对象的集合时,代码简单多了。你不用判断每个对象类型再调用对应方法,只要调用基类的通用方法,系统会自动选对实现。

就像你有一颗“开门”按钮,不管是普通门、推拉门还是旋转门,你都只按“开门”,具体怎么开门每种门自己搞定。

抽象

多态和抽象密不可分,抽象也是OOP的支柱之一(后面课会详细讲)。它让你关注“对象做什么”(比如“发出声音”、“体检”),而不是“怎么做”。你操作的是“动物”这个抽象概念,而不是具体的“狗”或“猫”。这样你能写出高层次、干净的代码,不用管底层细节。

代码复用

继承本身就能让你复用基类的属性和方法,多态更进一步。你可以写通用算法和数据结构(比如Animal[]),能操作所有继承自基类的对象,无需为每个子类重复逻辑。

本质上,多态让你的代码更“专业”,更能适应变化,这在真实开发里太重要了——需求天天变。

4. 有用的小细节

多态结构图

classDiagram
    class Animal {
        +MakeSound()
    }
    class Dog {
        +MakeSound()
    }
    class Cat {
        +MakeSound()
    }
    class Parrot {
        +MakeSound()
    }

    Animal <|-- Dog
    Animal <|-- Cat
    Animal <|-- Parrot
多态的类继承结构图

通过MakeSound()调用Animal类型的引用时,实际会调用变量里对象所属类的方法。

用多态搞定数组和集合

很常见的需求——遍历一堆不同类型的对象,对它们都执行同一套逻辑。


// 在我们的主练习应用里:
Animal[] zoo = { new Dog("小球"), new Cat("小虎"), new Parrot("小鹦") };

foreach (Animal animal in zoo)
{
    animal.MakeSound();
}

在实际项目里,这种写法常用来做事件处理、屏幕绘图(每种图形自己画)、支付处理(不同卡和服务)等等。

多态是怎么工作的

对象类型 变量类型 调用哪个方法?
Dog
Animal
Dog.MakeSound()
Cat
Animal
Cat.MakeSound()
Parrot
Animal
Parrot.MakeSound()
Animal
Animal
Animal.MakeSound()

重点——基类方法要virtualabstract,子类要override

5. 多态常见错误

现实中,学生经常会犯这些错:

  • 基类方法没写virtual——结果多态没生效,永远只会有一种行为。
  • 搞混了变量类型和类:记住,变量有类型(比如Animal),你放进去的对象是实例(比如new Dog())。
  • 想通过基类变量用子类才有的新成员。比如:

Animal pet = new Dog();
pet.Bark(); // 报错!Animal里没有Bark()

怎么解决? 实在要用可以强制类型转换,但最好还是只用基类里有的方法,结构清晰才不容易出错。

2
任务
C# SELF, 第 21 级, 课程 0
已锁定
简单演示多态
简单演示多态
2
任务
C# SELF, 第 21 级, 课程 0
已锁定
动物“检查”
动物“检查”
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION