显式实现接口
本课属于 Coddy C# 学习旅程的“面向对象编程”单元。第 28 课,共 70 课。
当一个类实现多个接口,而这些接口包含同名成员时,就会出现命名冲突。显式接口实现可以让你为每个接口分别提供实现,从而解决这个问题。
显式实现时,你需要在成员名称前加上接口名称,并省略访问修饰符:
public interface IAmericanPlug
{
void Connect();
}
public interface IEuropeanPlug
{
void Connect();
}
public class UniversalAdapter : IAmericanPlug, IEuropeanPlug
{
void IAmericanPlug.Connect()
{
Console.WriteLine("Connected to 120V");
}
void IEuropeanPlug.Connect()
{
Console.WriteLine("Connected to 220V");
}
}显式实现的成员只能通过接口类型的变量访问,不能直接通过类本身访问:
var adapter = new UniversalAdapter();
// adapter.Connect(); // 无法编译!
IAmericanPlug american = adapter;
american.Connect(); // 已连接到 120V
IEuropeanPlug european = adapter;
european.Connect(); // 已连接到 220V当你想从类的公共 API 中隐藏接口成员时,这种技术也很有用。如果某个方法只有在将对象视为特定接口时才有意义,那么显式实现可以让类的表面保持简洁,同时仍然履行契约。
挑战
简单我们来构建一个报表生成系统,演示显式接口实现如何让同一个类根据所使用的接口提供不同的行为。你将创建一个文档,使其在显示和打印时采用不同的格式。
你将把代码分成 4 个文件:
IDisplayable.cs:在Reports命名空间中定义一个名为IDisplayable的接口。这个接口表示可以在屏幕上显示的内容。它应声明一个返回字符串的Render()方法。IPrintable.cs:在Reports命名空间中定义一个名为IPrintable的接口。这个接口表示可以发送到打印机的内容。它也应声明一个返回字符串的Render()方法。注意,这会与IDisplayable产生命名冲突!Report.cs:在Reports命名空间中定义一个Report类,同时实现IDisplayable和IPrintable。报表应具有Title属性(string)和Content属性(string),并通过构造函数设置这两个值。使用显式接口实现,让Render()提供不同的行为:- 用于显示时的渲染结果:
"[SCREEN] {Title}: {Content}" - 用于打印时的渲染结果:
"[PRINT] {Title} | {Content}"
- 用于显示时的渲染结果:
Program.cs:在主文件中,使用输入值创建一个Report。由于只能通过接口变量访问显式实现的方法,因此要将同一个报表分别存入IDisplayable变量和IPrintable变量,然后分别调用它们的Render(),以展示同一个对象会根据你使用的接口产生不同的输出。
你将收到两个输入:
- 报表标题
- 报表内容
按以下格式输出:
Display version:
{Render() via IDisplayable}
Print version:
{Render() via IPrintable}例如,如果输入是 Sales Report 和 Q4 revenue increased by 15%,输出应为:
Display version:
[SCREEN] Sales Report: Q4 revenue increased by 15%
Print version:
[PRINT] Sales Report | Q4 revenue increased by 15%注意,同一个 Report 对象会根据你用来访问它的接口引用而表现出不同的行为。这就是显式接口实现的作用:解决命名冲突,同时提供符合使用场景的行为!
亲手试试
using System;
using Reports;
class Program
{
public static void Main(string[] args)
{
// 读取输入
string title = Console.ReadLine();
string content = Console.ReadLine();
// TODO: 用标题和内容创建一个 Report 对象
// TODO: 将报告存入一个 IDisplayable 变量
// TODO: 将同一份报告存入一个 IPrintable 变量
// TODO: 调用 IDisplayable 的 Render(),输出显示版本
Console.WriteLine("Display version:");
// 在这里输出渲染结果
// TODO: 调用 IPrintable 的 Render(),输出打印版本
Console.WriteLine("Print version:");
// 在这里输出渲染结果
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C# 在线编译器