接口
这是什么:接口(interface)——一份"能力合同",只规定要会做什么,不规定怎么做。
为什么用:继承讲的是"我是你儿子,继承你的家产",可八竿子打不着的真狗和机器狗也都得会叫。接口让它们签同一份《会叫合同》,谁也不用当谁的儿子。
如何用:本章先写一份 IAnimal 接口,只写方法不写方法体,再让 Dog 签下它。
一纸合同:interface
接口只写"要会什么",不写"怎么做"——方法连方法体都没有:
interface IAnimal // 惯例:接口名用 I 开头
{
string Name { get; } // 必须有一个只读的 Name
void Speak(); // 必须会叫——至于怎么叫,合同不管
}
合同里只有成员的"形状",没有任何实现代码。
谁签约,谁实现
类用冒号 : 签约(和继承一个写法),然后必须把合同里的每一条都实现出来:
// 用接口类型装对象:只要是签约方,都能往里装
List<IAnimal> pets = new List<IAnimal>
{
new Dog("旺财"),
new RobotDog()
};
foreach (IAnimal pet in pets)
{
Console.WriteLine($"{pet.Name} 报到!");
pet.Speak();
}
class Dog : IAnimal // 签约:Dog 承诺实现 IAnimal 的一切
{
public string Name { get; }
public Dog(string name) => Name = name;
public void Speak() => Console.WriteLine($"{Name}:汪汪!");
}
class RobotDog : IAnimal // 和 Dog 毫无血缘,照样签约
{
public string Name { get; } = "机器狗";
public void Speak() => Console.WriteLine($"{Name}:汪!汪!(电子音)");
}
interface IAnimal
{
string Name { get; }
void Speak();
}
运行输出:
旺财 报到!
旺财:汪汪!
机器狗 报到!
机器狗:汪!汪!(电子音)
为什么要面向接口编程
| 对比 | 继承 class | 接口 interface |
|---|---|---|
| 关系 | 是一种(is-a),有血缘 | 会一套(can-do),签合同 |
| 数量 | 只能继承一个父类 | 可以同时实现多个接口 |
| 内容 | 字段 + 属性 + 方法(带实现) | 只有成员声明(合同条款) |
注意 List<IAnimal>:这个列表根本不关心里面装的是啥,只要求签约。明天店里来一只会叫的电子鸭子,写个新类签约,循环代码一个字不改就能用。代码依赖"合同"而不是"具体某一家",换实现、加功能都不伤筋动骨——这就是面向接口编程。
再签一份合同:IPettable
一个类可以同时实现多个接口,用逗号隔开:
interface IPettable
{
void Pet(); // 抚摸
}
class Dog : IAnimal, IPettable // Dog 同时签两份合同
{
public string Name { get; }
public Dog(string name) => Name = name;
public void Speak() => Console.WriteLine($"{Name}:汪汪!");
public void Pet() => Console.WriteLine($"{Name} 舒服地摇尾巴");
}
// 用法
IPettable p = new Dog("旺财");
p.Pet(); // 旺财 舒服地摇尾巴
记忆口诀
接口合同不带肉,签约类来补实现;List 只认 I 开头,谁来都往里装。
❓ 常见问题
Q1:接口里能写方法实现吗?
C# 8.0 开始支持接口里的默认方法实现(default),但入门阶段把接口理解成"纯合同"即可:只声明,不实现。具体实现由签约类负责。
Q2:类能同时继承一个父类和实现多个接口吗?
可以。写法:class Dog : Animal, IAnimal, IPettable——冒号后第一个是父类,后面是接口,用逗号隔开。
Q3:接口名为什么要加 I 前缀?
这是 C# 的惯例(convention),不是强制规范。IAnimal 一眼就能看出是接口,不是普通类。团队里统一遵循即可。
⚠️ 新手翻车现场
翻车一:签约类没实现合同里的所有成员
class Dog : IAnimal
{
public string Name { get; }
public Dog(string name) => Name = name;
// 漏掉了 Speak()!❌
}
问题:实现接口时,合同里的每一条都必须实现,漏一条编译就报错。
修正:补上 public void Speak() => ...。
翻车二:接口变量调用接口里没有的成员
IAnimal pet = new Dog("旺财");
pet.Bark(); // ❌ IAnimal 合同里没有 Bark,编译报错
问题:接口类型的变量只能调用接口里定义的成员。要调 Bark,得把变量类型改成 Dog,或者强制转换。
修正:Dog d = new Dog("旺财"); d.Bark();
翻车三:把接口当类 new
IAnimal a = new IAnimal(); // ❌ 接口不能直接实例化
问题:接口是合同,不是类,不能 new。必须 new 一个签约类(如 new Dog())。
修正:IAnimal a = new Dog("旺财");
🎯 什么时候用接口
| 场景 | 推荐 | 原因 |
|---|---|---|
| 不相关的类需要同一能力 | 接口 | 狗和机器狗没血缘,都能签 IAnimal |
| 一个类要有多种能力 | 接口 | 可同时实现多个接口 |
| 代码只关心"能做什么",不关心"是谁" | 接口 | List<IAnimal> 装任何签约方 |
📝 本章小结
- 接口是合同,只声明成员,不写实现
- 类用
:签约,必须实现合同里所有成员 - 一个类可实现多个接口,用逗号隔开
- 接口变量只能调接口里定义的成员
- 接口不能直接
new,得new签约类 - 记忆口诀:接口合同不带肉,签约类来补实现;List 只认 I 开头,谁来都往里装。
动手改
- 写一个
Cat : IAnimal,把它也装进pets,验证循环不用改 - 再定义一个
IPettable接口(约定void Pet()),让Dog同时签两份合同 - 删掉
Dog.Speak的实现再编译,看看编译器开出什么罚单
下一章:综合练习,开一家宠物店把五招全用上 🐸