1. 为什么要实现多个接口?
当你设计一个真实系统时,对象经常不只扮演一个“角色”——而是好几个。想象一下:你有一本电子书,不仅能看,还能编辑,甚至还能同步到云端。用面向对象编程(OOP)的话来说,这个对象就得实现好几个接口:
- IReadable —— 读内容。
- IWritable —— 编辑内容。
- ISyncable —— 和远程存储同步。
这时候,多接口实现就派上用场了。这是C#(其实整个OOP)的一大超能力,类没有的:你不能继承多个类,但接口你想实现多少都行。
现实生活的类比
想象一下公司里的员工。小明可以同时是:
- 程序员(写代码)
- 测试员(有时候帮别人测代码)
- 经理(规划冲刺或者一天喝多少咖啡)
这些“角色”职责完全不一样。但小明都能搞定!编程里也是这样:类实现接口,承担它们的“职责”。
2. 多接口实现的语法
很简单:声明类的时候用逗号分隔接口就行:
public interface IReadable
{
void Read();
}
public interface IWritable
{
void Write(string text);
}
public class Note : IReadable, IWritable
{
private string content = "";
public void Read()
{
Console.WriteLine("便签: " + content);
}
public void Write(string text)
{
content = text;
Console.WriteLine("便签已更新!");
}
}
在这个例子里,Note类实现了两个接口。所以它必须有两个方法的实现:Read和Write。
3. “我们”应用里的例子
在之前的例子里,我们做了个简单的银行应用来管理账户。现在假设:我们需要一个通用的文档类,可以打印(IPrintable)、保存到文件(ISavable),还可能发邮件(IEmailable)。
先定义接口:
public interface IPrintable
{
void Print();
}
public interface ISavable
{
void Save(string filePath);
}
public interface IEmailable
{
void Email(string toAddress);
}
一个全能的类:
public class Statement : IPrintable, ISavable, IEmailable
{
public string Content { get; set; }
public void Print()
{
Console.WriteLine("正在打印账单...");
Console.WriteLine(Content);
}
public void Save(string filePath)
{
// 用标准库里的File.WriteAllText。
File.WriteAllText(filePath, Content);
Console.WriteLine($"账单已保存到文件: {filePath}");
}
public void Email(string toAddress)
{
Console.WriteLine($"账单已发送到邮箱: {toAddress}(模拟)");
}
}
现在这个类你怎么用都行:
var stat = new Statement { Content = "本月操作: +1000 单位, -500 单位." };
stat.Print();
stat.Save("statement.txt");
stat.Email("boss@bank.corp");
顺便说一句,实际开发中,把这种对象传给只需要某个接口的方法特别方便,根本不用管它还有啥功能。比如打印方法只要参数是IPrintable,根本不用知道里面是不是“保存器”还是“发邮件的”。
4. 用接口引用:我的对象到底能干啥?
这就有点神奇了。当你实现了多个接口,你可以把对象当成它的某一个“合同”来用。比如:
IPrintable printable = new Statement { Content = "抽象讲座" };
printable.Print(); // 只能打印
// printable.Save("file.txt"); // 报错:IPrintable接口根本不知道Save。
但如果切换到另一个接口:
ISavable savable = printable as ISavable;
if (savable != null)
{
savable.Save("file.txt");
}
这种方式很适合把对象传给只需要某个接口的方法。这样耦合度低,代码超灵活:你可以加新实现,老代码都不用动。
5. 多接口实现和不同接口里有同名方法怎么办
这就有意思了!如果两个接口都要求有同名但意义不同的方法咋办?比如,想象一下咖啡机的接口:
public interface IStartable
{
void Start();
}
public interface IRunnable
{
void Start();
}
咖啡机既可以“可启动”(IStartable——开始做咖啡),也可以“可运行”(IRunnable——进入工作状态)。
默认实现(普通写法):
public class CoffeeMachine : IStartable, IRunnable
{
public void Start()
{
Console.WriteLine("咖啡机以两种角色启动!");
}
}
这种情况下,一个Start()实现“覆盖”了两个接口。
如果想要不同的意义呢?
可以用显式接口实现:
public class CoffeeMachine : IStartable, IRunnable
{
void IStartable.Start()
{
Console.WriteLine("启动:开始制作饮品!");
}
void IRunnable.Start()
{
Console.WriteLine("启动:机器进入工作模式。");
}
}
这种情况下,只有通过接口变量才能调用具体实现:
CoffeeMachine cm = new CoffeeMachine();
IStartable startable = cm;
startable.Start(); // 启动:开始制作饮品!
IRunnable runnable = cm;
runnable.Start(); // 启动:机器进入工作模式。
// cm.Start(); // 编译不过!Start不能直接通过类调用。
顺便说一句,.NET标准库里经常用这种写法——比如一个类实现了好几个不同框架的类似接口,每个都要不一样的行为。
6. 多接口实现和继承的区别
| 能力 | 类(继承) | 接口 |
|---|---|---|
| 基类型数量 | 只能一个 | 随便几个 |
| 继承代码 | 可以(有基础实现) | 不行,只能签名(除非有默认方法) |
| 保存状态 | 可以 | 不行 |
| 加新角色 | 不行(或者很麻烦,要用组合) | 很简单,实现接口就行 |
| 更适合… | “物理”层级 | “角色”/逻辑能力 |
7. 实战:混合型对象
多接口实现让你可以做出有独特“角色”组合的类,根本不用管继承那些事。
比如,在我们的银行应用里——你可以做一个类,既能保存自己信息,又能自检有效性,还能打印——全靠不同接口:
public interface IValidatable
{
bool Validate();
}
public class Check : IPrintable, ISavable, IValidatable
{
public string Data { get; set; }
public void Print()
{
Console.WriteLine("打印支票: " + Data);
}
public void Save(string filePath)
{
File.WriteAllText(filePath, Data);
Console.WriteLine("支票已保存: " + filePath);
}
public bool Validate()
{
return !string.IsNullOrEmpty(Data);
}
}
这种方式让你可以随便扩展架构,类需要啥角色就加啥接口。要加新“职责”——直接实现新接口就行。
8. 常见错误和坑
新手常犯的错:忘了实现接口的所有成员。编译器绝不放过你——直接报错,告诉你少了啥。遇到这种错很好理解:“类必须实现接口成员”。
第二个常见问题——方法可见性搞混了。如果你用了显式实现,这个方法不能通过类变量访问——只能通过接口变量。
还有一个常见情况——重构时:你改了接口(比如加了个方法),但没更新所有实现类。这样就会编译报错。所以设计接口时,尽量别在很多类用上之后再改。
GO TO FULL VERSION