CodeGym /课程 /C# SELF /创建类的继承体系

创建类的继承体系

C# SELF
第 20 级 , 课程 4
可用

1. 引言

想象一下没有继承体系的世界:成千上万个PersonAnimalVehicle类,全都互不相关。难怪程序员在这种世界里连午饭都活不到——肯定早就晕菜了!在实际项目里,我们经常需要一些对象能做共同的事(比如所有动物都会移动),但每个又有自己的特点(鱼会游,鸟会飞)。

正是类的继承体系让我们能表达实体之间的关系,让编程变成有创造力的事,而不是无休止的复制粘贴大战。

来看个例子。假设我们有个基类Animal。所有动物都会发出声音。但只有猫会喵,狗会汪,鹦鹉甚至能讲段子。我们想在代码里用继承体系表达这些。

基类


public class Animal
{
    public string Name { get; set; }

    public Animal(string name)
    {
        Name = name;
    }

    // 基础方法:可以在子类里重写
    public virtual void Speak()
    {
        Console.WriteLine("动物发出某种声音...");
    }
}

这里我们给Speak()方法加了virtual。这就像个提示:“嘿,子类们,想重写就重写吧!”

创建继承体系:派生类

现在我们来个Cat类,继承自Animal


public class Cat : Animal
{
    public Cat(string name) : base(name) { }

    // 重写Speak——猫可不会随便咆哮!
    public override void Speak()
    {
        Console.WriteLine($"{Name} 说:喵!");
    }
}

还有Dog类:

public class Dog : Animal
{
    public Dog(string name) : base(name) { }

    public override void Speak()
    {
        Console.WriteLine($"{Name} 说:汪!");
    }
}

如果有个普通动物不会说话?那就用基类,不用重写啥。

可视化——继承树


.               Animal
                /   \
             Cat    Dog
  • Animal —— 基类
  • Cat, Dog —— 子类(派生类)

2. 写点用到继承体系的代码

继续搞我们的控制台小程序。

假设我们有个动物集合,想让每个动物说点自己的话:

Animal[] zoo = new Animal[]
{
    new Cat("巴尔西克"),
    new Dog("雷克斯"),
    new Animal("神秘生物")
};

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

预期输出:

巴尔西克 说:喵!
雷克斯 说:汪!
动物发出某种声音...

就这样,靠继承体系和多态(我们马上详细讲,其实就是根据对象真实类型调用正确的方法),你的程序就变得灵活又好扩展。

3. 加点新方法和字段

好了,发声搞定了。但所有动物都一样太无聊了。比如猫有九条命,狗会叼棍子。

加点独特行为

在子类里可以加自己的方法和字段:


public class Cat : Animal
{
    public int Lives { get; private set; } = 9;

    public Cat(string name) : base(name) { }

    public override void Speak()
    {
        Console.WriteLine($"{Name} 说:喵!我有 {Lives} 条命。");
    }

    public void LoseLife()
    {
        if (Lives > 0)
        {
            Lives--;
            Console.WriteLine($"{Name} 少了一条命。剩下:{Lives}");
        }
        else
        {
            Console.WriteLine($"{Name} 已经用光所有命了!");
        }
    }
}

代码里用一下:

var barsik = new Cat("巴尔西克");
barsik.Speak();      // 巴尔西克 说:喵!我有 9 条命。
barsik.LoseLife();   // 巴尔西克 少了一条命。剩下:8

加新类:扩展“动物园”

你已经会写派生类了。比如加个鹦鹉:

public class Parrot : Animal
{
    public Parrot(string name) : base(name) { }

    public override void Speak()
    {
        Console.WriteLine($"{Name} 说:你好,人类!");
    }

    public void Repeat(string phrase)
    {
        Console.WriteLine($"{Name} 重复:{phrase}");
    }
}

现在你可以轻松扩展系统,老代码都不用动:

var keshka = new Parrot("科沙");
keshka.Speak();                 // 科沙 说:你好,人类!
keshka.Repeat("学习吧,学生!"); // 科沙 重复:学习吧,学生!

4. 比较动物的行为

类型 Speak()方法 自有字段 额外行为
Animal 有(virtual Name
Cat 有(override Lives LoseLife()
Dog 有(override
Parrot 有(override Repeat(string)

内存里的类继承体系(流程图)

Animal (Name)
 ├── Cat (Lives)
 ├── Dog
 └── Parrot (Repeat)

5. 实战:在我们的应用里用继承

来,把继承体系和应用结合下——比如我们有不同类型的任务:

  1. Task(基类):任何任务——有名字和完成状态。
  2. WorkTask(工作任务):除了上面那些,还有截止日期。
  3. HomeTask(家务任务):可以有优先级(“非常重要”,“一般般”)。

先写基类:

public class Task
{
    public string Title { get; set; }
    public bool IsCompleted { get; private set; }

    public Task(string title)
    {
        Title = title;
    }

    public virtual void Complete()
    {
        IsCompleted = true;
        Console.WriteLine($"任务 \"{Title}\" 完成了!");
    }
}

现在加个工作任务:

public class WorkTask : Task
{
    public DateTime Deadline { get; set; }

    public WorkTask(string title, DateTime deadline)
        : base(title)
    {
        Deadline = deadline;
    }

    public override void Complete()
    {
        base.Complete();
        Console.WriteLine($"截止日期:{Deadline:d}");
    }
}

还有家务任务:

public class HomeTask : Task
{
    public string Priority { get; set; }

    public HomeTask(string title, string priority)
        : base(title)
    {
        Priority = priority;
    }

    // 如果基类的Complete够用,可以不用重写
}

在程序里搞个任务列表:

List<Task> tasks = new List<Task>
{
    new WorkTask("发送报告", DateTime.Today.AddDays(2)),
    new HomeTask("洗碗", "非常重要"),
    new Task("读一读继承讲座")
};

foreach (Task task in tasks)
{
    Console.WriteLine($"任务:{task.Title}");
    task.Complete();
}

预期输出:

任务:发送报告
任务 "发送报告" 完成了!
截止日期:13.07.2025
任务:洗碗
任务 "洗碗" 完成了!
任务:读一读继承讲座
任务 "读一读继承讲座" 完成了!

看到没,多方便:所有任务放一起,统一处理,特殊行为该有就有。

6. 用继承时常见的坑

坑1:想重写没加virtual的方法。
如果基类方法没标virtual,子类就不能重写。这样多态就没了,继承体系也没啥用。

坑2:没有逻辑关系还硬继承。
没有实际联系的对象别用继承。比如圆形确实是图形,但作为交通工具就有点扯。除非在特殊场景(比如中世纪游戏)下才说得通。

坑3:继承层级太深。
如果类结构太深(5、6层甚至更多),代码就难读、难维护、难测试。这时候应该考虑用组合代替继承。

坑4:忘了调用基类构造函数。
在派生类加新属性时,容易忘了在构造函数里显式调用base(...)。这样会导致基类部分没初始化好,出各种难查的bug。

1
调查/小测验
继承的概念第 20 级,课程 4
不可用
继承的概念
继承和类的层次结构
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION