接口
用一段代码处理好几种东西,以及给类和给组件分别该用什么。
接口是关于"能做什么"的约定,而不是"是什么"。对着这个约定写代码,凡是守约的东西都能用。
public interface IShape
{
int Area();
}
public class Square : IShape
{
public int side;
public int Area()
{
return side * side;
}
}
public class Rect : IShape
{
public int width;
public int height;
public int Area()
{
return width * height;
}
}
拿着 IShape 的一方不需要知道它是哪一种,就能问出面积,答案来自真正在那里的那个类:
IShape shape = square ? (IShape)new Square() : new Rect();
int area = shape.Area();
这个选择可以在世界运行时才做。这就是接口最日常、也最有价值的用法:一段代码,很多种东西。
什么时候划算
任何你原本要对着一个"类型字段"写一长串 if 的地方。
- 背包里的物品,每一种使用后的行为都不一样
- 任务步骤,每一步判断完成的方式都不一样
- 加在玩家身上的效果,各有各的规则
- 武器,"开火"对每把武器意味着不同的事
用接口写好之后,加一种新物品就是加一个类,所有用到 IItem 的代码都不用改。
组件不一样:用基类
接口是给你在代码里创建的类用的。组件不同,因为你要把它拖到 GameObject 上,Unity 得把这个引用存下来,而 Unity 没法序列化接口字段。写成接口类型,这个字段根本不会出现在 Inspector 里。
所以当你想共享的东西是一个 behaviour 时,用抽象基类:
public abstract class Door : UdoniteBehaviour
{
public abstract void Open();
}
public class SlidingDoor : Door
{
public override void Open() { /* ... */ }
}
public class SwingingDoor : Door
{
public override void Open() { /* ... */ }
}
这样开关只要一个字段,两种门都能用:
public class Switch : UdoniteBehaviour
{
public Door door; // 推拉门和平开门都可以拖到这里
public override void Interact()
{
door.Open();
}
}
字段会出现在 Inspector 里,两种门都收,door.Open() 调用的是对的那一个。基类还能带上共享的状态和共享的代码,这是接口做不到的。
在普通的 Unity 里,出于同样的原因也是这么做的。
怎么选
| 你想要 | 用 |
|---|---|
| 让代码里创建的几个类遵守同一个约定 | 接口 |
| 让 GameObject 上的几个 behaviour 遵守同一个约定 | 抽象基类 |
| 共享状态或共享方法实现 | 抽象基类 |
基类本身也可以实现接口,所以确实需要的时候,一个 behaviour 两者兼得。