1. 引言
想象一下没有继承体系的世界:成千上万个Person、Animal、Vehicle类,全都互不相关。难怪程序员在这种世界里连午饭都活不到——肯定早就晕菜了!在实际项目里,我们经常需要一些对象能做共同的事(比如所有动物都会移动),但每个又有自己的特点(鱼会游,鸟会飞)。
正是类的继承体系让我们能表达实体之间的关系,让编程变成有创造力的事,而不是无休止的复制粘贴大战。
来看个例子。假设我们有个基类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. 实战:在我们的应用里用继承
来,把继承体系和应用结合下——比如我们有不同类型的任务:
- Task(基类):任何任务——有名字和完成状态。
- WorkTask(工作任务):除了上面那些,还有截止日期。
- 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。
GO TO FULL VERSION