7.7 KiB
接口
概念:
接口使用 interface 关键字定义
它主要用来规定一组==能力 / 规则 / 契约==
简单理解: 接口不关心“你是谁” 只关心“你能不能做这件事”
例如:
IFlyable 表示“会飞”
不管你是鸟、飞机还是飞行道具,只要实现了这个接口,就代表你拥有飞行能力
通常用法:
- 规定某些类必须实现哪些成员
- 表达一种能力,例如会飞、会攻击、可保存、可移动
- 让不同类型用同一套调用方式
- 弥补 C# 类只能单继承的问题,因为一个类可以实现多个接口
- 配合多态vob,让代码面向能力编程
语法
定义接口:
interface 接口名
{
void 方法名();
}
实现接口:
class 类名 : 接口名
{
public void 方法名()
{
}
}
命名习惯:
接口名通常以大写字母 I 开头
例如:IFlyable、IAttackable、ISaveable
基础案例
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());
理解:
IFlyable规定实现者必须有Fly()方法Bird和Plane类型不同,但都实现了IFlyableLetItFly不关心传进来的是鸟还是飞机,只关心它会不会飞
这就是接口常见用法: ==面向能力编程,而不是面向具体类型编程==
接口中的成员
常见接口成员:
interface IMovable
{
void Move();
int Speed { get; set; }
}
实现:
class Player : IMovable
{
public int Speed { get; set; }
public void Move()
{
Console.WriteLine("玩家移动");
}
}
接口中常见成员包括:
- 方法
- 属性
- 索引器
- 事件
初学阶段重点先掌握: 接口方法 + 接口属性
接口成员默认是 public
接口里这样写:
interface IAttackable
{
void Attack();
}
实现时必须写 public:
class Enemy : IAttackable
{
public void Attack()
{
Console.WriteLine("敌人攻击");
}
}
错误写法:
class Enemy : IAttackable
{
void Attack()
{
}
}
原因: 接口成员代表对外公开的契约 类实现接口时,成员访问级别不能比接口更低
一个类可以实现多个接口
C# 的类只能继承一个父类,但可以实现多个接口。
interface IMovable
{
void Move();
}
interface IAttackable
{
void Attack();
}
class Player : IMovable, IAttackable
{
public void Move()
{
Console.WriteLine("玩家移动");
}
public void Attack()
{
Console.WriteLine("玩家攻击");
}
}
理解:
Player 同时拥有“可移动”和“可攻击”两种能力
继承类和实现接口同时存在
写法:
class 子类名 : 父类名, 接口1, 接口2
{
}
注意顺序: ==先写父类,再写接口==
案例:
class Character
{
public string Name;
}
interface IAttackable
{
void Attack();
}
class Warrior : Character, IAttackable
{
public void Attack()
{
Console.WriteLine(Name + "挥剑攻击");
}
}
错误示意:
// class Warrior : IAttackable, Character
// {
// }
如果既要继承类,又要实现接口,父类必须写在最前面。
接口也可以继承接口
接口之间可以继承,而且可以继承多个接口。
interface IMovable
{
void Move();
}
interface IAttackable
{
void Attack();
}
interface IPlayerAction : IMovable, IAttackable
{
void UseItem();
}
实现 IPlayerAction 的类,必须实现:
Move()Attack()UseItem()
class Player : IPlayerAction
{
public void Move()
{
}
public void Attack()
{
}
public void UseItem()
{
}
}
接口引用和多态
接口也可以当变量类型使用。
IAttackable attacker = new Player();
attacker.Attack();
也可以作为方法参数:
void DoAttack(IAttackable attacker)
{
attacker.Attack();
}
好处:
调用方只依赖 IAttackable
任何实现了这个接口的对象都能传进来
DoAttack(new Player());
DoAttack(new Enemy());
这也是多态的一种表现。
接口和里氏替换原则
接口常用来拆分能力,避免不合理继承。
比如:
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("企鹅游泳");
}
}
这样:
Bird只表达“鸟”IFlyable只表达“会飞”- 不会飞的企鹅不用硬实现
Fly()
这个例子可以和里氏替换原则一起理解。
显式实现接口
当两个接口有同名方法,或者你不希望方法直接暴露在类对象上,可以显式实现接口。
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
class Player : IAttackable
{
void Attack()
{
}
}
接口成员默认是公开契约,实现时通常要写 public。
错误2:没有实现接口所有成员
interface IMovable
{
void Move();
int Speed { get; set; }
}
class Player : IMovable
{
public void Move()
{
}
}
Speed 没实现,所以会报错。
错误3:把接口当成类直接创建对象
IFlyable flyable = new IFlyable();
接口只是规则,不能直接 new。
正确写法:
IFlyable flyable = new Bird();
什么时候用接口
适合用接口的情况:
- 你要表达的是“能力”,不是“父子身份”
- 多个无关类型都有同一种行为
- 一个类型需要拥有多种能力
- 想让代码依赖规则,而不是依赖具体类
例如:
IFlyable:会飞IAttackable:能攻击ISaveable:可保存IDamageable:可受伤
注意
- 接口用
interface定义 - 接口名通常以
I开头 - 接口不能直接创建对象
- 类和结构体都可以实现接口
- 一个类可以实现多个接口
- 接口成员实现时通常要写
public - 接口更适合表达“能做什么”,抽象类更适合表达“是什么”
- 接口常和多态、里氏替换原则一起理解
一句话记忆
==接口是能力契约,谁实现它,谁就必须按规则提供这些能力。==
再压缩一点: ==接口管能不能做,不管你是谁。==
面试/复习时怎么说
可以这样答:
“接口是用 interface 定义的一组契约,主要用来规定实现类必须提供哪些成员。
类可以实现多个接口,所以接口常用来表达能力,比如可移动、可攻击、可保存。
调用方可以面向接口编程,只依赖能力规则,不依赖具体类,这样更利于扩展和多态。”
引用:
- Microsoft Learn: interface (C# Reference)
- Microsoft Learn: Interfaces
#接口 #核心