C#知识学习-011(interface)

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。这意味着无论它后面实际指向的是 SmartSpeakerPhone还是 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 访问

学到了这里,咱俩真棒,记得按时吃饭(希望你今天开心~,明天也是)

【本篇结束,新的知识会不定时补充】

感谢你的阅读!如果内容有帮助,欢迎 ​​点赞❤️ + 收藏⭐ + 关注​​ 支持! 😊