Home 什么是“Interfaces”以及如何使用
Post
Cancel

什么是“Interfaces”以及如何使用

在一款普通的RPG游戏当中,玩家常常需要对场景中的物体进行攻击,但一般来说场景中可以攻击的物体有很多,有敌人,有怪物,甚至是建筑,如果我们每次攻击都要判断被攻击的物体是不是上面所说的三类中的一类,将会很难进行管理。

因此我们希望可以通过某种方式来对这些可以被玩家攻击到的物体进行一个统一的标识,接口则是处理这类情形最适合的方法。

再这篇博客你将学习到以下的内容

  1. 什么是Interfaces
  2. 如何创建一个Interfaces
  3. 在Unity当中应该如何使用Interfaces

什么是Interface

Unity的接口我们可以近似的理解为一个函数列表。

当类实现接口的时候,这个类必须公开的包含这些函数,使用与接口中编写的相同的方法名称,相同的参数以及相同的返回类型。 接口作为一种约束,让使用它的脚本必须执行在接口内所定义的规则,因此对于一些脚本而言,继承了接口的类可以被近似的看成同一类型的类,即使他们本质上并不同。

举个例子,在我们上面提到的RPG游戏的例子,玩家可以对怪物发动攻击,对建筑发动攻击,对敌人发动攻击,我们希望当玩家攻击的时候,被攻击的物体可以依次实现GetHurt()方法,而不必考虑被攻击物体到底是怪物建筑和敌人中的哪一个。因此我们这里将建筑,怪物,敌人统一视作“可以被人物攻击到”的物体。

即使建筑,怪物,敌人收到伤害的方式可能不太一样,但是我们任然可以调用一样的函数来进行执行。

如何在Unity中创建Interface

在Unity当中创建Interface和在Unity当中创建Scripts有着一样的步骤。

一般对于Interface的命名开头我们通常以一个大写的I来开头,用interface来表示这个文件是一个接口。

1
2
3
4
public interface ICanHit
{

}

然后我们会在这个接口中声明我们需要使用的方法和变量。在我们这篇文章讲的例子当中,我们希望它拥有GetHurt()的方法,但是你在编写函数方法的时候不需要将函数的权限设置为public,也不用包含函数的主体。就像下面的代码所显示的。

1
2
3
4
public interface ICanHit
{
    void GetHurt();
}

对于那些我们希望被视作为“可以被人物攻击到”的物体。我们将他们用语法来实现我们刚刚创建好的接口。

1
2
3
4
5
6
public class Enemy : MonoBehaviour, ICanHit
{
    public void GetHurt(){
        //Enemy受到了伤害
    }
}

他必须要像我们在接口中定义的函数那样,具有一样的名字,一样的返回类型,以及可以公开进行访问。但是函数的实现部分是Enemy所独有的,你可以在其中添加动画,血量减少的enmey独有的逻辑。建筑类和怪物类也使用相同的方法来进行继承。

所以在Unity当中我们希望当玩家攻击的时候可以通过接口来进行判断物体是否可以被伤害,我们可以使用TryGetComponent来进行实现。

1
2
3
4
5
6
public void PlayerAttack(GameObject object)
{
    if(object.TryGetComponent(out ICanHit canHit)){
        object.GetHurt();
    }
}

什么时候该使用Interfaceas

与接口行为类似的是我们在面向对象编程当中常用的继承。

但是两者的工作方式存在着本质的差异,继承更偏向于是一种“是什么”的关系,而接口更偏向于一种“能干什么”的关系。

因此对于敌人和可被破坏的建筑来说,除了都可以被玩家攻击到,基本没有其他的共同点,所以这个时候用接口定义他们可以干什么的行为,显然开发起来更加容易。

可是如果是一个男哥布林和一个女哥布林,他们除了性别其他行为都是一致的,所以如果这个时候我们使用接口的话需要重复写两次重复的代码,而且当我们需要改变哥布林受伤逻辑的时候,我们需要在两个地方进行修改,大大减缓了我们开发的速度。

所以在上面这种情形下,单一的使用接口和单一的使用继承很难满足我们的所有需求,因此最佳的使用方式我们将接口和继承来进行组合使用。

像上述例子的男哥布林以及女哥布林都让他们继承于怪物这个基类,然后我们在基类当中实现ICanHurt接口,这样继承于这个基类的子类也会同时实现ICanHurt所定义的方法,而且我们如果需要修改逻辑,也只需要在基类一处地方进行修改。如下图所示

如果你看过我在b站上关于组合模式的视频你会很惊喜的发现,接口似乎可以和Unity当中Component一样当作组件,来实现不同行为的组合。

其实接口和Unity当中的Component没有什么区别,都可以用来实现设计模式当中的组合模式,两者的区别是,接口将所有的方法和功能都集中在一个脚本当中,但是Component却将不同的行为分开成为两个脚本上来进行实现。

因此在使用Component的时候要在脚本之间进行必要的通讯,而接口则不需要进行这种脚本层面的通讯,但是接口很容易将一个类集成十分多复杂的功能。不利于阅读很分模块。

两种方式都由其优点以及缺点,需要根据具体的例子来进行不同的选择。

This post is licensed under CC BY 4.0 by the author.