7. 面向对象——多态
7.1 多态
什么是多态?
多态即“多种状态”,它可以让继承于同一父类的子类在执行相同方法时有不同的行为状态(不同的函数执行方式),例如同一个爹的不同儿子有着不同的性格行为,但他们都是这个爹的儿子。
多态有什么用?
多态可以让继承同一父类的每一个对象调用同一方法时有唯一的方法执行逻辑,实现每一个子类均为独立个体,拥有独立行为(用父类接收的子类对象可以调用自己的函数而非父类中的函数)。
如何使用多态进行代码书写?
首先要记住三个关键字vob(virtual
、override
、base
),其中virtual
关键字用于定义父类方法,他将该方法声明为虚方法,子类可以进行重写以实现子类的特殊执行,在子类重写父类虚方法时,需要加上override
关键字,代表这个方法为重写的父类方法,如果需要调用父类中的代码逻辑,在函数中可以用base
来进行调用。
例如有父类GameObject
和子类Player
、Enemy
:
public class GameObject
{
public string name;
public GameObject(string name)
{
this.name = name;
}
public virtual void Attack(GameObject gameObject)//父类中的虚方法
{
Console.WriteLine(name + "进行了攻击");
}
}
public class Player : GameObject
{
public Player(string name) : base(name) {
}
public override void Attack(GameObject gameObject)//对父类虚方法进行重写
{
Console.WriteLine(name + "对" + gameObject.name + "进行了攻击");
}
}
public class Enemy : GameObject
{
public Enemy(string name) : base(name) {
}
public override void Attack(GameObject gameObject)//对父类虚方法进行重写
{
base.Attack(gameObject);//调用父类方法
Console.WriteLine(gameObject.name + "受到了攻击");
}
}
对他们进行实例化并调用:
GameObject player = new Player("泠曦");
GameObject enemy = new Enemy("老战士格雷特");
player.Attack(enemy);
enemy.Attack(player);
运行结果:
泠曦对老战士格雷特进行了攻击
老战士格雷特进行了攻击
泠曦受到了攻击
7.2 抽象类和抽象函数abstract
什么是抽象类和抽象函数?
简单来讲抽象类是一个不能被实例化的类,只允许被其他类继承,而抽象函数则只能写在抽象类中,他不能有任何实现。例如人类和每个单独的人,人类一般不会拿来代指某一个个体。
为什么需要抽象类和抽象函数?
抽象类和抽象函数可以用于定义一些比较宽泛的概念如人、水果、动物等,他们不是用来描述单一个体,而是用来描述一系列的对象,不能进行具体实例化的类和函数。使用抽象类和抽象函数可以用于定义这些较为抽象的概念而不必去具体的实现他(例如我们无法直接描述动物是怎么进食的,也无法直接描述水果应该如何食用,但他们确实具备这样的功能或方法),这样可以对一系列类进行方法函数的规范,保证继承后的对象表面特征的一致性,具体实现的多样性。
已经有虚函数为什么还需要抽象函数?
虚函数可以有函数体,可以在函数体中写入一些基础的处理逻辑,但虚函数在被子类继承时,可以不对其进行重写,如果写了一个空虚函数,在未进行重写的情况下运行将会出现错误,对于已经写了预处理逻辑的函数,如果子类改变了部分处理方法,运行时也可能会出现错误,并且因为子类中没有重写该函数而出现难以排查的结果。
抽象函数没有函数体,只需要定义方法名及参数,不关心具体实现,子类必须对其进行重写,避开了前面虚函数的未重写错误或难以排查问题所在的问题(实现函数必定会在子类当中)。
如何书写抽象类和函数?
抽象类和函数都由abstract
关键字修饰,抽象类在class
前加上关键字,抽象函数则在返回之前添加,但是需要注意抽象函数不能有具体函数体实现:
//抽象类
public abstract class Fruits
{
public int price;//抽象类中可以定义变量、属性等
public Fruits(int price)
{
this.price = price;
}
//抽象方法 不能有具体实现
public abstract void EatFruit();
}
如何实现抽象类及抽象函数?
实现抽象类只需要继承该类即可,继承后如果含有抽象函数将会报错,此时可以手动添加抽象函数或右键报错位置,选择实现抽象类
VS将会自动实现所有抽象函数,只需要填充对应函数体即可:
//继承抽象类
public class Orange : Fruits
{
public Orange(int price) : base(price) {
}
//进行方法重写
public override void EatFruit()
{
Console.WriteLine("吃了一个价值为" + price + "的橘子");
}
}
public class Durian : Fruits
{
public Durian(int price) : base(price) {
}
public override void EatFruit()
{
Console.WriteLine("打开了一个价值" + price + "的榴莲并将榴莲壳作为了防具");
}
}
进行实例化并调用:
Fruits fruit = new Orange(3);
fruit.EatFruit();
fruit = new Durian(100);
fruit.EatFruit();
吃了一个价值为3的橘子
打开了一个价值100的榴莲并将榴莲壳作为了防具
7.3 接口interface
什么是接口?
接口(interface)是多态的一种体现,他可以看成一种特殊的只包含抽象函数和属性的抽象类,但是与抽象类不同,接口是一种行为方式的集合,代编着一种特殊的行为,例如飞行
这个行为,它可以是鸟的行为,也可以是飞机的行为,这里可以将飞行作为接口来定义。
接口有什么用?
接口定义了行为,继承并实现该接口的类相当于具有了这样的行为,通过接口可以对这些行为函数进行规范,同时接口又可以当成他们的基类使用,同一种接口可以接收飞机和鸟这两个除了能飞之外完全不同的类对象,实现了按功能区分不同类。
如何让定义接口/定义接口时需要注意什么?
定义接口关键字为interface
,默认访问修饰符为public
并且只能为public
,接口当中的字段可以为public
或protected
,默认为public
,接口中的函数不能进行定义,只能在实现接口的类中进行定义。
例如书写一个飞行接口:
public interface IFly
{
public float Speed {
get; set; }
public void Fly();
}
如何实现接口?
实现接口与继承抽象类大致相同,但不同的是需要实现接口内的所有字段,并且只能为public
,实现的函数可以为虚函数,子类继承是可以再次进行重写。
例如实现上面的接口:
public class Flight : IFly
{
public float Speed {
get; set; }
public virtual void Fly()//时限为虚函数
{
Console.WriteLine("飞机以时速为" + Speed + "的速度在飞行。");
}
}
public class Bird : IFly
{
public float Speed {
get; set; }
public void Fly()
{
Console.WriteLine("小鸟正在飞行。");
}
}
一个类最多可以实现多少接口?
与继承类这种一个孩子只能由一个父母不同,一个对象它可以有多种行为方法,所以一个类可以实现多个接口。
接口可以继承接口吗?
接口就可以继承接口,并且不用进行函数等的实现,他仅仅相当于继承了对应的行为。
同一个接口可以继承多个不同的接口,例如:
如果不同接口中出现了同名函数,应该如何进行实现和访问?
当不同接口中出现同名函数时,实现接口需要使用显式实现的方法,语法为接口名.方法名
,但是显示实现的函数不能有访问修饰符,并且不能通过类实例进行访问。例如有两个接口包含同名函数:
interface IWalk
{
public void Move();
}
interface IRun
{
public void Move();
}
实现这个接口时,显式实现:
public class People : IWalk, IRun
{
void IWalk.Move()//实现IWalk
{
Console.WriteLine("Walk");
}
void IRun.Move()//实现IRun
{
Console.WriteLine("Run");
}
}
访问显式实现后的函数,需要通过接口而不是通过类对象:
无法通过实例找到方法:
可以通过接口访问:
访问时结果与分别对应的实现一致:
可以不显式实现同名函数吗?
可以直接书写一个函数进行实现,但如果不显式实现对应函数,将会导致不同接口调用执行效果一致,违背了接口设计时对不同行为进行分离的初衷。
7.4 密封方法Sealed Function
什么是密封方法?
密封方法就是对父类函数进行重写时使用sealed
关键字修饰的函数,他让这个函数不可以再次被重写。
什么时候会用到密封方法?
当我们重写一个函数后不希望子类对他再次重写破坏掉原有逻辑时,可以使用蜜蜂方法。
如何定义密封方法?
定义密封方法只需要在重写关键字前加上sealed关键字即可:
public class Boeing : Flight
{
public sealed override void Fly()
{
base.Fly();
}
}
重写后如果再有类继承该类,将无法进行重写: