# 接口 概念: 接口使用 `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) #接口 #核心