# 接口
概念：
	接口使用 `interface` 关键字定义
	它主要用来规定一组==能力 / 规则 / 契约==

简单理解：
	接口不关心“你是谁”
	只关心“你能不能做这件事”

例如：
	`IFlyable` 表示“会飞”
	不管你是鸟、飞机还是飞行道具，只要实现了这个接口，就代表你拥有飞行能力

通常用法：
1. 规定某些类必须实现哪些成员
2. 表达一种能力，例如会飞、会攻击、可保存、可移动
3. 让不同类型用同一套调用方式
4. 弥补 C# 类只能单继承的问题，因为一个类可以实现多个接口
5. 配合[[多态vob|多态]]，让代码面向能力编程

----
# 语法
定义接口：
```csharp
interface 接口名
{
	void 方法名();
}
```

实现接口：
```csharp
class 类名 : 接口名
{
	public void 方法名()
	{

	}
}
```

命名习惯：
	接口名通常以大写字母 `I` 开头
	例如：`IFlyable`、`IAttackable`、`ISaveable`

----
# 基础案例
```csharp
interface IFlyable
{
	void Fly();
}

class Bird : IFlyable
{
	public void Fly()
	{
		Console.WriteLine("鸟在飞");
	}
}

class Plane : IFlyable
{
	public void Fly()
	{
		Console.WriteLine("飞机在飞");
	}
}

void LetItFly(IFlyable flyable)
{
	flyable.Fly();
}

LetItFly(new Bird());
LetItFly(new Plane());
```

理解：
1. `IFlyable` 规定实现者必须有 `Fly()` 方法
2. `Bird` 和 `Plane` 类型不同，但都实现了 `IFlyable`
3. `LetItFly` 不关心传进来的是鸟还是飞机，只关心它会不会飞

这就是接口常见用法：
	==面向能力编程，而不是面向具体类型编程==

----
# 接口中的成员
常见接口成员：
```csharp
interface IMovable
{
	void Move();
	int Speed { get; set; }
}
```

实现：
```csharp
class Player : IMovable
{
	public int Speed { get; set; }

	public void Move()
	{
		Console.WriteLine("玩家移动");
	}
}
```

接口中常见成员包括：
1. 方法
2. 属性
3. 索引器
4. 事件

初学阶段重点先掌握：
	接口方法 + 接口属性

----
# 接口成员默认是 public
接口里这样写：
```csharp
interface IAttackable
{
	void Attack();
}
```

实现时必须写 `public`：
```csharp
class Enemy : IAttackable
{
	public void Attack()
	{
		Console.WriteLine("敌人攻击");
	}
}
```

错误写法：
```csharp
class Enemy : IAttackable
{
	void Attack()
	{
	}
}
```

原因：
	接口成员代表对外公开的契约
	类实现接口时，成员访问级别不能比接口更低

----
# 一个类可以实现多个接口
C# 的类只能继承一个父类，但可以实现多个接口。

```csharp
interface IMovable
{
	void Move();
}

interface IAttackable
{
	void Attack();
}

class Player : IMovable, IAttackable
{
	public void Move()
	{
		Console.WriteLine("玩家移动");
	}

	public void Attack()
	{
		Console.WriteLine("玩家攻击");
	}
}
```

理解：
	`Player` 同时拥有“可移动”和“可攻击”两种能力

----
# 继承类和实现接口同时存在
写法：
```csharp
class 子类名 : 父类名, 接口1, 接口2
{

}
```

注意顺序：
	==先写父类，再写接口==

案例：
```csharp
class Character
{
	public string Name;
}

interface IAttackable
{
	void Attack();
}

class Warrior : Character, IAttackable
{
	public void Attack()
	{
		Console.WriteLine(Name + "挥剑攻击");
	}
}
```

错误示意：
```csharp
// class Warrior : IAttackable, Character
// {
// }
```

如果既要继承类，又要实现接口，父类必须写在最前面。

----
# 接口也可以继承接口
接口之间可以继承，而且可以继承多个接口。

```csharp
interface IMovable
{
	void Move();
}

interface IAttackable
{
	void Attack();
}

interface IPlayerAction : IMovable, IAttackable
{
	void UseItem();
}
```

实现 `IPlayerAction` 的类，必须实现：
1. `Move()`
2. `Attack()`
3. `UseItem()`

```csharp
class Player : IPlayerAction
{
	public void Move()
	{
	}

	public void Attack()
	{
	}

	public void UseItem()
	{
	}
}
```

----
# 接口引用和多态
接口也可以当变量类型使用。

```csharp
IAttackable attacker = new Player();
attacker.Attack();
```

也可以作为方法参数：
```csharp
void DoAttack(IAttackable attacker)
{
	attacker.Attack();
}
```

好处：
	调用方只依赖 `IAttackable`
	任何实现了这个接口的对象都能传进来

```csharp
DoAttack(new Player());
DoAttack(new Enemy());
```

这也是多态的一种表现。

----
# 接口和里氏替换原则
接口常用来拆分能力，避免不合理继承。

比如：
```csharp
abstract class Bird
{
	public string Name;
}

interface IFlyable
{
	void Fly();
}

class Sparrow : Bird, IFlyable
{
	public void Fly()
	{
		Console.WriteLine("麻雀飞");
	}
}

class Penguin : Bird
{
	public void Swim()
	{
		Console.WriteLine("企鹅游泳");
	}
}
```

这样：
1. `Bird` 只表达“鸟”
2. `IFlyable` 只表达“会飞”
3. 不会飞的企鹅不用硬实现 `Fly()`

这个例子可以和[[里氏替换原则]]一起理解。

----
# 显式实现接口
当两个接口有同名方法，或者你不希望方法直接暴露在类对象上，可以显式实现接口。

```csharp
interface IWalkable
{
	void Move();
}

interface IFlyable
{
	void Move();
}

class Bird : IWalkable, IFlyable
{
	void IWalkable.Move()
	{
		Console.WriteLine("鸟在走");
	}

	void IFlyable.Move()
	{
		Console.WriteLine("鸟在飞");
	}
}

Bird bird = new Bird();

IWalkable walkable = bird;
walkable.Move();

IFlyable flyable = bird;
flyable.Move();
```

注意：
	显式实现接口时，不写 `public`
	调用时通常要先把对象当成对应接口类型

初学阶段先知道有这个写法即可，最常用的还是普通实现接口。

----
# 常见错误
错误1：实现接口时忘记 `public`
```csharp
class Player : IAttackable
{
	void Attack()
	{
	}
}
```

接口成员默认是公开契约，实现时通常要写 `public`。

----
错误2：没有实现接口所有成员
```csharp
interface IMovable
{
	void Move();
	int Speed { get; set; }
}

class Player : IMovable
{
	public void Move()
	{
	}
}
```

`Speed` 没实现，所以会报错。

----
错误3：把接口当成类直接创建对象
```csharp
IFlyable flyable = new IFlyable();
```

接口只是规则，不能直接 `new`。

正确写法：
```csharp
IFlyable flyable = new Bird();
```

----
# 什么时候用接口
适合用接口的情况：
1. 你要表达的是“能力”，不是“父子身份”
2. 多个无关类型都有同一种行为
3. 一个类型需要拥有多种能力
4. 想让代码依赖规则，而不是依赖具体类

例如：
- `IFlyable`：会飞
- `IAttackable`：能攻击
- `ISaveable`：可保存
- `IDamageable`：可受伤

----
# 注意
1. 接口用 `interface` 定义
2. 接口名通常以 `I` 开头
3. 接口不能直接创建对象
4. 类和结构体都可以实现接口
5. 一个类可以实现多个接口
6. 接口成员实现时通常要写 `public`
7. 接口更适合表达“能做什么”，抽象类更适合表达“是什么”
8. 接口常和多态、里氏替换原则一起理解

----
# 一句话记忆
==接口是能力契约，谁实现它，谁就必须按规则提供这些能力。==

再压缩一点：
==接口管能不能做，不管你是谁。==

----
# 面试/复习时怎么说
可以这样答：

“接口是用 `interface` 定义的一组契约，主要用来规定实现类必须提供哪些成员。  
类可以实现多个接口，所以接口常用来表达能力，比如可移动、可攻击、可保存。  
调用方可以面向接口编程，只依赖能力规则，不依赖具体类，这样更利于扩展和多态。”

----
引用：
1. Microsoft Learn: [interface (C# Reference)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interface)
2. Microsoft Learn: [Interfaces](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/interfaces)

#接口
#核心
