C++纯虚接口

1、引言

最近想到了很久以前做软件设计开发时的经历,当时公司项目很多,大多类似但是又略有区别,最初每个项目都会新建一个仓库,开发起来很快,但是后期维护还是挺麻烦的。经过分析发现这些项目有很多代码都是可以复用的,核心算法都是同一套,区别主要是相机个数,控制器类别、通信方式以及一些业务逻辑。当时主要就是用了继承和接口隔离把这一系列不同的项目代码融合到同一套程序中,不同的项目只需要通过配置文件配置一下,然后专注实现项目业务相关的一个类就好了。实现以上这些最为关键的就是纯虚接口的使用。

2、接口隔离原则

2.1 概述

接口隔离原则是设计模式中提到的五大原则之一,李建忠老师的设计模式讲的非常好。接口隔离原则的核心思想是:

客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。

通俗地说,接口应该小而精 ,而不是大而全。将臃肿的接口拆分成更小的、更具体的接口,每个接口只负责一类功能。

2.2 为什么需要接口隔离

实现好的接口隔离可以降低耦合度,提高代码可维护性,便于复用组合。最开始设计的时候没有用纯虚接口,在多继承调用的时候好像还出问题了,后来部分模块就改成了纯虚接口。

2.2.1 反例:臃肿的接口

cpp 复制代码
// 不好的设计:一个庞大的接口
class IWorker {
public:
    virtual void work() = 0;
    virtual void eat() = 0;
    virtual void sleep() = 0;
    virtual void code() = 0;
    virtual void design() = 0;
    virtual ~IWorker() = default;
};

// 机器人不需要吃饭睡觉,但被迫实现
class Robot : public IWorker {
public:
    void work() override { /* ... */ }
    void eat() override { /* 无法实现或抛异常 */ }      // 违反 ISP
    void sleep() override { /* 无法实现或抛异常 */ }     // 违反 ISP
    void code() override { /* ... */ }
    void design() override { /* ... */ }
};

2.2.2 正例:隔离后的接口

cpp 复制代码
// 拆分成多个小接口
class IWorkable {
public:
    virtual void work() = 0;
    virtual ~IWorkable() = default;
};

class IFeedable {
public:
    virtual void eat() = 0;
    virtual ~IFeedable() = default;
};

class ISleepable {
public:
    virtual void sleep() = 0;
    virtual ~ISleepable() = default;
};

class ICodable {
public:
    virtual void code() = 0;
    virtual ~ICodable() = default;
};

// 人类:需要所有功能
class Human : public IWorkable, public IFeedable, public ISleepable, public ICodable {
    void work() override { /* ... */ }
    void eat() override { /* ... */ }
    void sleep() override { /* ... */ }
    void code() override { /* ... */ }
};

// 机器人:只需要部分功能
class Robot : public IWorkable, public ICodable {
    void work() override { /* ... */ }
    void code() override { /* ... */ }
};

2.3 通用 DEMO:工业控制系统接口设计

2.3.1 场景描述

设计一个工业控制系统,需要支持多种设备(传感器、执行器、控制器等),每种设备有不同的能力。

2.3.2 接口定义

cpp 复制代码
#pragma once
#include <string>
#include <vector>
#include <memory>

// ============================================================================
// 小型化、专业化的接口定义
// ============================================================================

// 1. 数据读取接口
class IDataReader {
public:
    virtual double readData() = 0;
    virtual ~IDataReader() = default;
};

// 2. 数据写入接口
class IDataWriter {
public:
    virtual void writeData(double value) = 0;
    virtual ~IDataWriter() = default;
};

// 3. 开关控制接口
class ISwitchable {
public:
    virtual void turnOn() = 0;
    virtual void turnOff() = 0;
    virtual bool isOn() const = 0;
    virtual ~ISwitchable() = default;
};

// 4. 状态查询接口
class IStatusQuery {
public:
    virtual std::string getStatus() = 0;
    virtual int getErrorCode() = 0;
    virtual ~IStatusQuery() = default;
};

// 5. 校准接口
class ICalibratable {
public:
    virtual void calibrate() = 0;
    virtual void resetCalibration() = 0;
    virtual ~ICalibratable() = default;
};

// 6. 配置接口
class IConfigurable {
public:
    virtual void setConfig(const std::string& key, const std::string& value) = 0;
    virtual std::string getConfig(const std::string& key) = 0;
    virtual ~IConfigurable() = default;
};

// 7. 事件订阅接口
class IEventSubscriber {
public:
    virtual void subscribe(const std::string& event) = 0;
    virtual void unsubscribe(const std::string& event) = 0;
    virtual ~IEventSubscriber() = default;
};

// ============================================================================
// 设备实现:按需组合接口
// ============================================================================

// 温度传感器:只需要读取数据、查询状态
class TemperatureSensor : public IDataReader, public IStatusQuery {
public:
    double readData() override {
        return 25.5; // 模拟读取温度
    }

    std::string getStatus() override {
        return "OK";
    }

    int getErrorCode() override {
        return 0;
    }
};

// 继电器:只需要开关控制、查询状态
class Relay : public ISwitchable, public IStatusQuery {
public:
    void turnOn() override { isOn_ = true; }
    void turnOff() override { isOn_ = false; }
    bool isOn() const override { return isOn_; }

    std::string getStatus() override {
        return isOn_ ? "ON" : "OFF";
    }

    int getErrorCode() override { return 0; }

private:
    bool isOn_ = false;
};

// 智能执行器:需要所有功能
class SmartActuator : public IDataReader, public IDataWriter,
                       public ISwitchable, public IStatusQuery,
                       public ICalibratable, public IConfigurable {
public:
    // IDataReader
    double readData() override { return position_; }

    // IDataWriter
    void writeData(double value) override { position_ = value; }

    // ISwitchable
    void turnOn() override { enabled_ = true; }
    void turnOff() override { enabled_ = false; }
    bool isOn() const override { return enabled_; }

    // IStatusQuery
    std::string getStatus() override { return enabled_ ? "RUNNING" : "STOPPED"; }
    int getErrorCode() override { return errorCode_; }

    // ICalibratable
    void calibrate() override { /* 执行校准 */ }
    void resetCalibration() override { /* 重置校准 */ }

    // IConfigurable
    void setConfig(const std::string& key, const std::string& value) override {
        config_[key] = value;
    }

    std::string getConfig(const std::string& key) override {
        auto it = config_.find(key);
        return it != config_.end() ? it->second : "";
    }

private:
    double position_ = 0.0;
    bool enabled_ = false;
    int errorCode_ = 0;
    std::map<std::string, std::string> config_;
};

// 简单 LED 指示灯:只需要开关控制
class LedIndicator : public ISwitchable {
public:
    void turnOn() override { isOn_ = true; }
    void turnOff() override { isOn_ = false; }
    bool isOn() const override { return isOn_; }

private:
    bool isOn_ = false;
};

2.4 多继承场景的应用

2.4.1 优势一:灵活的能力组合

cpp 复制代码
// 客户端代码:按需组合
class ControlSystem {
public:
    // 只需要开关能力的设备
    void controlSwitch(ISwitchable& device) {
        device.turnOn();
    }

    // 只需要读取能力的设备
    double monitor(IDataReader& sensor) {
        return sensor.readData();
    }

    // 需要多种能力的设备
    void fullControl(ISwitchable& sw, IDataReader& rd, IStatusQuery& st) {
        sw.turnOn();
        auto data = rd.readData();
        auto status = st.getStatus();
    }
};

2.4.2 优势二:接口多重继承实现"能力叠加"

cpp 复制代码
// 定义复合接口:继承多个小接口
class IFullDevice : public ISwitchable, public IStatusQuery, public ICalibratable {
    // 不需要额外代码,自动继承所有接口
};

// 实现类只需继承复合接口
class FullDevice : public IFullDevice {
    void turnOn() override { /* ... */ }
    void turnOff() override { /* ... */ }
    bool isOn() const override { return true; }
    std::string getStatus() override { return "OK"; }
    int getErrorCode() override { return 0; }
    void calibrate() override { /* ... */ }
    void resetCalibration() override { /* ... */ }
};

2.5 纯虚接口的优势总结

2.5.1 多继承支持 (Multiple Inheritance)

  • C++ 支持多继承,一个类可以实现多个接口
  • 每个接口代表一种"能力",按需组合
  • 避免单继承语言(如 Java)需要大量 adapter 模式的问题
cpp 复制代码
class Device : public ISwitchable, public IDataReader, public IStatusQuery {
    // 一个类拥有三种能力
};

2.5.2 零开销抽象 (Zero-Overhead Abstraction)

  • 纯虚接口不含成员变量,只有虚函数表指针
  • 没有额外内存开销
  • 虚函数调用开销在现代 CPU 上可接受

2.5.3 依赖倒置 (Dependency Inversion)

  • 高层模块依赖抽象接口,不依赖具体实现
cpp 复制代码
// 高层模块
class Controller {
    IDataReader& sensor_;  // 依赖接口
public:
    Controller(IDataReader& sensor) : sensor_(sensor) {}
};

2.5.4 开闭原则 (Open-Closed Principle)

  • 新增设备类型时,不需要修改现有接口
  • 扩展新功能时,添加新接口而非修改旧接口
cpp 复制代码
// 新增功能:添加日志接口
class ILogger {
public:
    virtual void log(const std::string& msg) = 0;
    virtual ~ILogger() = default;
};

// 现有代码无需修改
class DeviceWithLog : public IDataReader, public ILogger {
    // ...
};

2.5.5 接口隔离 (Interface Segregation)

  • 客户端只依赖它需要的接口
  • 避免"胖接口"导致的实现负担

2.5.6 编译时多态 + 运行时多态

  • 可以用模板实现编译时接口检查
  • 也可以用虚函数实现运行时多态
cpp 复制代码
// 编译时多态(CRTP 模式)
template<typename T>
class Switchable {
public:
    void toggle() {
        static_cast<T*>(this)->turnOn();
    }
};

class Led : public Switchable<Led> {
public:
    void turnOn() { /* ... */ }
};

2.5.7 清晰的契约 (Clear Contract)

  • 接口明确定义了"能做什么",不含"怎么做"
  • 代码自文档化,易于理解

2.5.8 便于 Mock 和测试

  • 测试时可以用 Mock 实现替换真实设备
cpp 复制代码
// 测试用 Mock 传感器
class MockSensor : public IDataReader {
    double mockValue_;
public:
    MockSensor(double value) : mockValue_(value) {}
    double readData() override { return mockValue_; }
};

// 单元测试
TEST(DeviceTest, ShouldReadData) {
    MockSensor sensor(42.0);
    Controller controller(sensor);
    ASSERT_EQ(42.0, controller.read());
}

2.5.9 动态能力组合 (Runtime Composition)

  • 可以在运行时决定使用哪些接口
cpp 复制代码
void processDevice(std::vector<std::shared_ptr<IStatusQuery>> devices) {
    for (auto& device : devices) {
        std::cout << device->getStatus() << std::endl;
    }
}

2.5.10 向后兼容 (Backward Compatibility)

  • 接口变更时,可以通过继承扩展而非修改
cpp 复制代码
// 旧接口
class IDeviceV1 {
    virtual void operate() = 0;
};

// 新接口继承旧接口,添加新功能
class IDeviceV2 : public IDeviceV1 {
    virtual void operateAdvanced() = 0;
};

// 旧实现仍然有效
class LegacyDevice : public IDeviceV1 {
    void operate() override { /* ... */ }
};

// 新实现支持高级功能
class ModernDevice : public IDeviceV2 {
    void operate() override { /* ... */ }
    void operateAdvanced() override { /* ... */ }
};

2.6 总结

特性 说明
多继承支持 C++ 核心优势,灵活组合能力
零开销 虚函数表指针,无额外内存
依赖倒置 高层依赖抽象,便于测试
开闭原则 扩展不修改,易于维护
接口隔离 客户端最小依赖,代码清晰
Mock 友好 便于单元测试
向后兼容 接口版本演进容易

接口隔离原则结合 C++ 的多继承机制,可以构建出高内聚、低耦合、易扩展的软件架构。

相关推荐
代码探秘者2 小时前
【Java】浅拷贝 VS 深拷贝:核心差异 + 实现方式 + 避坑指南
java·开发语言
永远睡不够的入2 小时前
C++list详解
c++·windows·list
Joker Zxc2 小时前
【前端基础(Javascript部分)】5、JavaScript的循环语句
开发语言·前端·javascript
不会写DN2 小时前
Golang中实时推送的功臣 - WebSocket
开发语言·后端·golang
仰泳的熊猫2 小时前
题目2268:蓝桥杯2016年第七届真题-密码脱落
数据结构·c++·算法·蓝桥杯
星辰_mya2 小时前
无锁编程:并发的“珠穆朗玛峰”与 F1 的“无缝换挡”
java·开发语言·面试
温柔一只鬼.2 小时前
Java GUI 制作 贪吃蛇小游戏
java·开发语言
Yvonne爱编码2 小时前
二叉树高频题精讲 | 从入门到熟练掌握二叉树操作
java·开发语言·数据结构·链表·二叉树
kaikaile19952 小时前
基于PCNN和NSCT的图像融合MATLAB实现
开发语言·图像处理·算法·matlab