C#教程3:继承、重载与多态

继承 Inheritance
假设我们想设计一个狗的类,它需要包含Color和Species属性,以及Eat()和Sleep()方法,我们通常会这样做:
public class Dog
{
//1.属性,颜色和品种
public string Color { get; set; }
public string Species { get; set; }
//2.构造函数
public Dog(string color, string species)
{
Color = color;
Species = species;
}
//方法
public void Eat()
{
Console.WriteLine($"我是一只{Color}的{Species},我在吃东西!");
}
public void Sleep()
{
Console.WriteLine($"我是一只{Color}的{Species},我在睡觉!");
}
}
现在,如果我们想设计一个类似的狼的类,我们大概率也会这么做:
public class Wolf
{
//属性,颜色和品种
public string Color { get; set; }
public string Species { get; set; }
//构造函数
public Wolf(string color, string species)
{
Color = color;
Species = species;
}
//方法
public void Eat()
{
Console.WriteLine($"我是一只{Color}的{Species},我在吃东西!");
}
public void Sleep()
{
Console.WriteLine($"我是一只{Color}的{Species},我在睡觉!");
}
}
对比上面两段代码,我们发现,大部分代码是一样的;不一样的地方仅仅是类的名字(和构造函数的名字);如果下次我们再设计一个老虎类,同样的代码又要再写一次…如果这样的话,重复的代码我们写了又写,我们写的代码将非常的臃肿,无聊!那么,怎么解决这个问题呢?
我们发现我们写的属性和方法基本上所有动物都具备,因此我们将这些所有动物共有的属性和方法封装到一个动物类(Animal)里面。
public class Animal
{
public string Color { get; set; }
public string Species { get; set; }
//构造函数
public Animal(string color, string species)
{
Color = color;
Species = species;
}
public void Eat()
{
Console.WriteLine($"我是一只{Color}的{Species},我在吃东西!");
}
public void Sleep()
{
Console.WriteLine($"我是一只{Color}的{Species},我在睡觉!");
}
}
有了这个Animal类,我们可以使用继承来复用这些公用的属性和方法(代码)了
继承建立了类之间的层次结构,反映了现实世界中的"是一种"关系。
继承的语法如下:
public class 子类名 : 父类名
{
}
首先,创建一个Dog类,继承自Animal类
public class Dog : Animal
{
//狗的构造函数:base(color, species)表示调用父类的构造函数
public Dog(string color, string species) : base(color, species)
{
}
}
然后,创建一个Wolf类,同样继承自Animal类
public class Wolf : Animal
{
//狼的构造函数:base(color, species)表示调用父类的构造函数
public Wolf(string color, string species) : base(color, species)
{
}
}
虽然Dog类和Wolf类都内部只写了一个构造函数,但是由于它们都继承自Animal,因此Animal中非私有的属性和方法这两个子类都可以使用。
//创建一个Dog类的对象
Dog dog1 = new Dog("黄色", "狗", "旺财");
//由于Dog类继承自Animal类,因此Dog类生成的对象可以访问Animal类中的属性
Console.WriteLine($"我是一只{dog1.Color}的{dog1.Species}!");
//也可以调用Animal类中的方法
dog1.Eat();
//创建一个Wolf类的对象
Wolf wolf1 = new Wolf("黑色", "狼",false);
//由于Wolf类也继承自Animal类,因此Wolf类生成的对象也可以访问Animal类中的属性
Console.WriteLine($"我是一只{wolf1.Color}的{wolf1.Species}!");
//也可以调用Animal类中的方法
wolf1.Eat();
如果后续我们还想创建老虎类、大象类和其他动物类,我们同样只需要让它们继承Animal类即可。
如此,我们通过继承提高了代码的复用性!
再思考一下,不同动物有不同的属性,比如,如狗一般有名字,而狼没有;不同的动物可以做不同的事情(方法),比如狼会捕猎,而狗不会。
因此,我们在继承Animal类后,我们可以也应对Dog类和Wolf类做进一步的设计,使其具备一些其他类所不具备的属性或方法。我们把这个称之为扩展。
接下来,我们来扩展设计一下我们的Dog类,使其具有一些狗所特有的属性或方法:
public class Dog : Animal
{
//狗特有的属性:名字
public string Name { get; set; }
//狗的构造函数:base(color, species)表示调用父类的构造函数
public Dog(string color, string species, string name) : base(color, species)
{
Name = name;
}
//狗特有的属性:名字
public void Bark()//狗特有的方法:汪汪叫
{
Console.WriteLine("我是一只狗,我会汪汪叫!");
}
}
也将Wolf类修改一下:
public class Wolf : Animal
{
//狼特有的属性:是否是群体的领头
public bool IsAlpha { get; set; }
//狼的构造函数。base(color, species)表示调用父类的构造函数
public Wolf(string color, string species,bool isAlpha) : base(color, species)
{
IsAlpha = isAlpha;
}
//狼特有的方法:捕猎
public void Hunt()
{
Console.WriteLine("我是一只狼,我会捕猎!");
}
}
改成这样之后,我们实列化的Dog对象都有Name属性和Bark方法,而实例化的Wolf对象都有IsAlpha属性和Hunt方法。
//狗有自己的属性
Console.WriteLine($"我的名字叫{dog1.Name}");
//也有自己的方法
dog1.Bark();
//狼也有自己的属性
Console.WriteLine($"我是群体的领头吗?{wolf1.IsAlpha}");
//狼也有自己的方法
wolf1.Hunt();
//错误,狗没有捕猎的方法
dog1.Hunt();
//错误,狼没有汪汪叫的方法
wolf1.Bark();
这样的话,我们设计的Dog和Wolf既利用继承复用了Animal父类的属性和方法(代码),又扩展出了自己独特的属性和方法(代码)。
继承可以是多层次的,比如,我们可以让Dog类也派生出不同的子类:
//哈士奇
public class Husky:Dog
{
}
//柯基
public class Corgi:Dog
{
}
//泰迪
public class Teddy:Dog
{
}
同样,我们也可以让Wolf派生出不同的子类
//灰狼
public class GreyWolf:Wolf
{
}
//平原狼
public class PlainsWolf:Wolf
{
}
//森林狼
public class TimberWolf:Wolf
{
}
我们可以用UML类图来表示类的层级结构
最上层类Animal是所有动物的基类,它用于定义所有动物公用的属性和方法;
狗继承自Animal,它是一种Animal,它有Animal的属性和方法,也有自己的属性和方法;
狼也继承自Animal,它也是一种Animal,它有Animal的属性和方法,也有自己的属性和方法;
狗和狼都是Animal的子类;
狗和狼又都有自己的子类;
通过这种层级继承的结构,我们模仿显示世界中**“是一种”**的逻辑,代码看起来清晰易懂,也极大的提高了代码的复用性,
重载 Overloading
继续看上面的例子,我们在Animal类中定义了吃的方法,继承该类的所有子类均使用这个方法,所以狼和狗吃的方法都是一样的,然而,这与实际的情况不符合!狗是家养动物,是杂食性的,而狼是野生动物,是肉食性的,它们吃的方法不应该一样。
我们可以利用重载来解决这个问题,首先在Animal的Eat()方法上添加virtual关键字,把它变成虚函数,虚函数运行后代将其重写。
public class Animal
{
public string Color { get; set; }
public string Species { get; set; }
//构造函数
public Animal(string color, string species)
{
Color = color;
Species = species;
}
public virtual void Eat()
{
Console.WriteLine($"我是一只{Color}的{Species},我在吃东西!");
}
public void Sleep()
{
Console.WriteLine($"我是一只{Color}的{Species},我在睡觉!");
}
}
接着,我们在Dog类中利用override关键字重写一些Dog类版本的Eat()方法
public class Dog : Animal
{
//狗特有的属性:名字
public string Name { get; set; }
//狗的构造函数:base(color, species)表示调用父类的构造函数
public Dog(string color, string species, string name) : base(color, species)
{
Name = name;
}
//狗特有的属性:名字
public void Bark()//狗特有的方法:汪汪叫
{
Console.WriteLine("我是一只狗,我会汪汪叫!");
}
//重写Eat方法
public override void Eat()
{
Console.WriteLine($"我是一只{Color}的{Species},我在吃骨头!");
}
}
然后,我们在Wolf类中同样利用override关键字重写一些Wolf类版本的Eat()方法
public class Wolf : Animal
{
//狼特有的属性:是否是群体的领头
public bool IsAlpha { get; set; }
//狼的构造函数。base(color, species)表示调用父类的构造函数
public Wolf(string color, string species,bool isAlpha) : base(color, species)
{
IsAlpha = isAlpha;
}
//狼特有的方法:捕猎
public void Hunt()
{
Console.WriteLine("我是一只狼,我会捕猎!");
}
//重写Eat方法
public override void Eat()
{
Console.WriteLine($"我是一只{Color}的{Species},我在吃小兔子!");
}
}
接下来,我们看到,同样是调用Eat()方法,狗和狼都调用了自己的版本,而不是Animal父类中定义的版本
//狗吃的方法,重写了父类,用自己的方法
dog1.Eat();
//狼吃的方法,重写了父类,用自己的方法
wolf1.Eat();
多态 Polymorphism
利用重载,我们可以实现多态。
什么是多态?同一种对象,调用同一个方法,但是运行的版本各不一样。
//因为Dog继承自Animal,所以我们在创建Dog对象时,把它声明为Animal类型
Animal dog2 = new Dog("黑色", "狗", "小黑");
//类似的,因为Wolf继承自Animal,所以我们在创建Wof对象时,也可以把它声明为Animal类型
Animal wolf2 = new Wolf("白色", "狼", false);
//接下来,我们创建一个Animal类型的列表
List<Animal> animals = new List<Animal>();
//然后把刚才创建的两个动物(狗dog2和狼wolf2)添加进行
animals.Add(dog2);
animals.Add(wolf2);
//最后,我们循环遍历这些动物
foreach(var animal in animals)
{
//让他们都运行Eat方法
//通过输出,我们可以看到,同样的一行代码,输出却不一样,这就是多态!
animal.Eat();
}
作业
作业一:交通工具系统
设计一个简单的交通工具类层次结构,要求如下:
-
创建一个基类
Vehicle
(交通工具),包含以下成员:- 属性:品牌(Brand)、型号(Model)、最高速度(MaxSpeed)
- 方法:
Start()
、Stop()
、Move()
(设为虚方法)
-
创建至少三个子类:
Car
(汽车)、Bicycle
(自行车)、Boat
(船),它们都继承自Vehicle
:- 每个子类添加特有的属性(如
Car
可以有NumberOfDoors
) - 每个子类重写
Move()
方法,实现自己的移动方式 - 每个子类添加一个特有的方法(如
Car
可以有Honk()
)
- 每个子类添加特有的属性(如
-
在
Main
方法中:- 创建各种交通工具的对象
- 调用它们的方法展示继承和多态
- 创建一个
Vehicle
类型的数组,存储不同的交通工具对象,并循环调用它们的方法
作业二:形状计算器
设计一个形状计算器,用于计算不同形状的面积和周长,要求如下:
-
创建一个抽象基类
Shape
(形状),包含以下成员:- 属性:颜色(Color)
- 抽象方法:
CalculateArea()
、CalculatePerimeter()
- 普通方法:
DisplayInfo()
,用于显示形状的信息
-
创建至少三个子类:
Circle
(圆)、Rectangle
(矩形)、Triangle
(三角形),它们都继承自Shape
:- 每个子类添加必要的属性(如
Circle
需要Radius
) - 每个子类实现抽象方法
CalculateArea()
和CalculatePerimeter()
,计算各自的面积和周长 - 每个子类可以重写
DisplayInfo()
方法,添加特有的信息
- 每个子类添加必要的属性(如
-
在
Main
方法中:- 创建各种形状的对象
- 计算并显示它们的面积和周长
- 创建一个
Shape
类型的数组,存储不同的形状对象,并循环计算它们的面积和周长
评论 (0)
暂无评论,来发表第一条评论吧!