1.本质
接口 (interface
) 是一个纯粹的规范。 它只定义 "必须有什么" ,不定义 "具体怎么做"
想象一下你要买一个"能播放音乐的设备"。你不在乎它是手机、MP3 播放器还是电脑,你只在乎它必须能做 一件事:播放音乐。
- 接口 (
interface
) 就像这份"设备功能说明书"。它只规定 这个设备必须有哪些功能(方法) ,比如PlayMusic()
。 - 类 (
class
)、记录 (record
)、结构 (struct
) 就像是具体的设备制造商(比如苹果、索尼)。它们签了这份合同 (实现了接口),就必须按照说明书的要求 ,在自己的产品(类)里具体实现PlayMusic()
这个方法。苹果手机怎么播放音乐(用扬声器?用蓝牙?)和索尼播放器怎么播放(插耳机?)是它们各自内部的事情,但说明书只要求"能播放"这个结果。
2.目的
2.1 多态性
你可以使用接口类型的变量 来引用任何实现了该接口的具体类型的对象 。通过这个接口变量,你只能调用接口中定义的那些成员。同一个接口调用 ,在不同对象上会产生不同的行为(因为具体实现不同)。
解释:这个概念比较抽象,我会举一个具体的例子帮你理解它
假如你有一个万能遥控器 。这个遥控器上只有几个基本按钮:开机 、关机。
接口就是遥控器的按钮说明书:
public interface IRemoteControl
{
void TurnOn(); // 开机按钮的规格
void TurnOff(); // 关机按钮的规格
}
具体类型的对象就是不同的电器:
public class TV : IRemoteControl // 电视机承诺能用遥控器控制
{
public void TurnOn()
{
Console.WriteLine("电视机:屏幕亮起...");
}
public void TurnOff()
{
Console.WriteLine("电视机:屏幕变黑,进入待机状态...");
}
}
public class AirConditioner : IRemoteControl // 空调也承诺能用遥控器控制
{
public void TurnOn()
{
Console.WriteLine("空调:滴一声,开始吹风...");
}
public void TurnOff()
{
Console.WriteLine("空调:停止送风,进入休眠...");
}
}
现在开始分解刚才的概念:
"你可以使用接口类型的变量( 就像你手里的万能遥控器)来引用任何实现了该接口的具体类型的对象( 就像电视机或 空调 )。"
用接口变量引用具体对象: 你可以用同一个遥控器,先让它指向 电视机,再让它指向 空调。
// 声明一个"遥控器类型"的变量 remote,IRemoteControl(接口类型)
IRemoteControl remote;
remote = new TV(); // remote "指向" 电视机对象 (万能遥控器对准电视)
// ... 稍后 ...
remote = new AirConditioner(); // remote "指向" 空调对象 (万能遥控器对准空调)
"通过这个接口变量,你只能调用接口中定义的那些成员。"
当你拿着万能遥控器时,你只能按说明书 (IRemoteControl
) 上定义的按钮 :TurnOn()
和 TurnOff()
。你不能 通过 remote
去:
- 调电视机的频道,因为遥控器说明书上没这个按钮。
- 调空调的温度 ,因为遥控器说明书上也没这个按钮。
"同一个接口调用( 你按下了遥控器上的同一个 TurnOn()
按钮**),在不同对象上会产生不同的行为**
- 当
remote
指向电视机 时,按下TurnOn()
:电视机:屏幕亮起 - 当
remote
指向空调 时,按下同一个TurnOn()
按钮:// 输出:空调:滴一声,开始吹风
虽然你按的是遥控器上同一个按钮 (TurnOn()
) ,但这个按钮信号发送到了不同的电器对象 。电视机对象内部的 TurnOn()
方法代码是让电视开机,空调对象内部的 TurnOn()
方法代码是让空调开机。具体怎么"开机",是由那个被指向的对象自己决定的!
2.2 解耦
**代码可以依赖于接口(规范),而不是依赖于具体的实现类。**这使得代码更灵活,更容易修改和扩展。你可以更换实现类而不影响依赖于接口的代码。
- 解释:写代码的人(比如写打开程序的人)只需要知道"这东西能打开"(它实现了
TurnOn
接口),不需要知道空调或者电视怎么打开的具体细节。这样代码更灵活,更易更换设备类型。
3.关键特点
3.1 只声明,不实现
-
接口主要用来定义方法名、参数和返回值类型 ,但不写方法里面具体做什么。
-
接口声明了一组成员(方法、属性、事件、索引器)的签名。
- 签名: 成员的名字、参数类型(如果有)、返回值类型(如果有)。
- 没有实现: 接口本身不包含这些成员的具体代码逻辑。
-
例子:
interface IPlayMusic { void Play(); // 声明一个Play 的方法,没有参数,不返回值(void) void Stop(); // 声明一个Stop 的方法 string CurrentSong { get; } // 声明一个只读属性,获取当前歌曲名 }
这个
IPlayMusic
接口规定:任何签了这份合同的设备(类),都必须有Play()
,Stop()
方法和一个CurrentSong
属性。
3.2 实现接口的类必须提供具体实现
-
如果一个类型声明实现了某个接口,它就必须为该接口中声明的每一个成员(没有默认实现的)提供具体的实现代码。 这是编译器强制要求的。
-
类、结构、记录可以通过
:
语法声明它们实现了某个或多个接口。 -
例子:
class SmartSpeaker : IPlayMusic // SmartSpeaker 类实现了 IPlayMusic 接口 { private string _currentSong; // 内部字段,存储当前歌曲 // 实现接口要求的 Play 方法 public void Play() { Console.WriteLine("智能音箱开始播放音乐..."); // 音箱播放音乐的具体代码 } // 实现接口要求的 Stop 方法 public void Stop() { Console.WriteLine("智能音箱停止播放音乐。"); // 音箱停止播放的具体代码 } // 实现接口要求的 CurrentSong 只读属性 public string CurrentSong { get { return _currentSong; } } }
4.使用接口
你可以创建一个接口类型的变量,然后用实现了该接口的类的实例来赋值。通过这个接口变量,你只能调用接口中定义的那些方法/属性。
例子:
// 创建一个 IPlayMusic 类型的变量,指向一个 SmartSpeaker 对象
IPlayMusic myPlayer = new SmartSpeaker();
myPlayer.Play(); // 调用 Play 方法 -> 输出 "智能音箱开始播放音乐..."
Console.WriteLine("播放: " + myPlayer.CurrentSong); // 访问CurrentSong属性
myPlayer.Stop(); // 调用 Stop 方法 -> 输出 "智能音箱停止播放音乐。"
// 注意:如果 SmartSpeaker 自己有个 VolumeUp() 方法
//你不能直接用 myPlayer.VolumeUp() 调用
关键点: myPlayer
变量的类型是 IPlayMusic
。这意味着无论它后面实际指向的是 SmartSpeaker
、Phone
还是 MP3Player
对象,**只要这些对象实现了 IPlayMusic
,我就可以安全地调用 Play()
, Stop()
, CurrentSong
。**我不需要关心具体是哪种设备在播放。
5.显式接口实现
有时候,一个类实现了多个接口,而这些接口可能有同名同参数的方法。或者,你想隐藏 接口方法的实现,让它只能通过接口访问,不能通过类实例直接访问。
语法:在方法名前加上接口名和一个点 .
显式实现解决了名字冲突的问题,并且把接口方法的访问限制在了通过接口引用的方式。
例子:
interface IDrawOnScreen
{
void Draw();
}
interface IDrawOnPaper
{
void Draw();
}
class MultiPurposePrinter : IDrawOnScreen, IDrawOnPaper
{
// 显式实现 IDrawOnScreen.Draw
void IDrawOnScreen.Draw()
{
Console.WriteLine("Drawing on the screen...");
}
// 显式实现 IDrawOnPaper.Draw
void IDrawOnPaper.Draw()
{
Console.WriteLine("Printing on paper...");
}
}
只能通过接口访问:
MultiPurposePrinter printer = new MultiPurposePrinter();
// 通过接口调用显式实现的方法
IDrawOnScreen screenPrinter = printer;
screenPrinter.Draw(); // 输出 "Drawing on the screen..."
IDrawOnPaper paperPrinter = printer;
paperPrinter.Draw(); // 输出 "Printing on paper..."
// 不能直接用 printer 调用 IDrawOnScreen.Draw 或 IDrawOnPaper.Draw
// printer.IDrawOnScreen.Draw(); // 错误!
6.接口成员
6.1 默认接口成员
-
以前接口成员绝对不能有实现。现在允许接口为方法或属性提供默认实现 。
-
实现这个接口的类可以选择 使用这个默认实现,也可以自己重写 它。
-
例子:
// 开关接口(带默认实现) interface ISwitchable { // 默认实现:通用的打开方式 void TurnOn() { Console.WriteLine("设备已启动"); } } // 灯泡选择使用默认实现(不用自己写TurnOn) class LightBulb : ISwitchable { // 这里什么都不用写!自动继承默认实现 } // 风扇选择重写默认实现(提供自己的特殊实现) class Fan : ISwitchable { // 显式重写默认实现 public void TurnOn() { Console.WriteLine("风扇转动"); } }
使用时:
ISwitchable bulb = new LightBulb(); bulb.TurnOn(); // 输出:设备已启动 (使用默认实现) ISwitchable fan = new Fan(); fan.TurnOn(); // 输出:风扇转动 (使用自定义实现)
6.2 静态成员
静态成员**(static
)不属于任何实例** ,通过接口名直接访问,举例:
public interface IShape
{
static readonly double PI = 3.14159;// 静态成员:一个常量 (PI)
}
使用:
double piValue = IShape.PI; // 直接通过接口名 IShape 访问
学到了这里,咱俩真棒,记得按时吃饭(希望你今天开心~,明天也是)
【本篇结束,新的知识会不定时补充】
感谢你的阅读!如果内容有帮助,欢迎 点赞❤️ + 收藏⭐ + 关注 支持! 😊