前篇链接:Unity之C#学习笔记(9):抽象类和抽象方法 Abstract Classes and Methods
深入理解接口:为什么放弃了多继承?接口是什么?
在上一节末尾,我们提出了一个问题:对于一个现有的类,如果想对其扩展,让它支持某种“特性”,该怎么做?很自然的想法是利用继承,使这个类继承于包含了满足这个特性的方法的基类。然后可能正如你刚学接口的概念时发现的,Java和C#都不支持多继承,一个类只能有一个基类。有的地方还会告诉你,接口解决了这个问题。没错,的确是解决了,但为什么在C++中被允许的多继承,到了Java和C#却被取消了呢?
显然,这说明多继承是有缺点的。最突出的问题就是,多继承会使你的类之间的关系变得复杂混乱,导致诸如菱形继承引发的二义性等问题的发生。反思一下,继承的初衷,是为了表现类之间“is a”的关系,例如用抽象类做一个“模板”就是很好的例子。但“实现一个单独的特性”并不是这种关系。说到底,它根本不应该用一个类来描述。我们现在就需要这样一种描述了“特性”的“规格”的类型。
应运而生的就是接口(Interface)。如何理解接口:接口是一种“Contract”,一个“契约”。契约规定了要实现的内容,具体来说就是一些函数的格式(像抽象函数那样)。任何类,如果你“签”了(实现了)这样一份契约,你就要把契约中规定的内容(那些函数)实现出来。这样,当其他人拿着这份契约来找人时,你就是一个符合要求的对象。
接口不是一个类,用关键字interface修饰:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IDamagable
{
void Damage(int damageAmount);
}
Tips:Java和C#对接口有不同的命名规范。在Java中,接口名称一般是以小写字母开头的形容词,例如throwable,serializable。而在C#中,接口名称以大写字母“I”开头,紧接一个大写字母开头的形容词,例如IComparable或上例中的IDamagable。
这里,我们声明了一个接口IDamagable,代表可以被伤害,并在其中规定了一个方法Damage。我们不需要去实现这个方法。谁实现了这个接口,谁就要去把这个方法实现出来。这样,我们就强迫实现IDamagable接口的类去实现了Damage方法。反过来说,我们任何通过IDamagable特性可以找到的对象,都是有Damage这个方法的。而通过TakeDamage方法,我们就可以实现伤害的效果。
接口不可以被实例化。在接口中,不能有变量,只能有属性和方法,而且方法不能给出实现。所有方法都默认为public。关于属性我们会在后面的部分讲到,如果不了解的同学可以暂时理解为:如果在接口中涉及到对变量的操作,也就是我们本来可能想通过接口访问到变量,就要以属性的方式声明。属性名应该以大写开头。我们为上面的例子添加一个属性:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IDamagable
{
int Health {
get; set; }
void Damage(int damageAmount);
}
get; set; 代表这个属性既可以被读,也可以被改写。这样,实现IDamagable接口的类还必须有一个Health属性,它可以对应到具体的变量。
现在接口定义好了,我们创建一个Player和一个Enemy物体和对应的脚本,让它们实现这个接口。语法很简单,在继承的基类后面加一个逗号,再写接口名即可(而在Java中,继承和接口分别要使用extends和implements关键字)。然后我们立刻看到VS的报错:
这就是在提示我们必须要实现Health属性和Damage方法。在Damage方法中,我们让Player的health变量减去一定量,并且颜色变为红色。对于Enemy,我们让它变成绿色。
using System