在C++中,“依赖接口编程”(Dependency Inversion Principle, DIP)是面向对象设计中的六大设计原则之一,属于SOLID原则的最后一个字母D。它强调的是高层模块不应该依赖于低层模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象


一、核心思想详解

1.1 什么是“依赖接口编程”?

传统的程序结构通常是:

高层模块(业务逻辑) → 直接调用 → 低层模块(具体实现)

例如:一个医疗上位机软件直接调用某个特定的串口通信类来读取设备数据。

问题在于:一旦底层模块变化(比如从串口换成网口),高层模块也必须修改和重新编译,耦合度高,难以维护。

而“依赖接口编程”的解决方案是:

高层模块 ← 依赖于 → 抽象接口(如IDataSource)
低层模块 ← 实现 → 抽象接口(如SerialDataSource, TcpDataSource)

这样,高层模块不关心底层具体怎么实现,只通过接口操作,实现了解耦


1.2 两个关键点

  1. 高层模块和低层模块都依赖于抽象
    • 高层不直接依赖具体的低层实现。
  2. 抽象不依赖于细节,细节依赖于抽象
    • 接口定义行为,不关心实现方式。
    • 具体类去实现这个接口。

✅ 正确做法:class DeviceManager 依赖 IDataCommunicator 接口
❌ 错误做法:DeviceManager 直接 new SerialPortTcpClient


二、实现思路(C++)

2.1 使用抽象基类(纯虚函数)

// 抽象接口:数据通信器
class IDataCommunicator {
public:
    virtual ~IDataCommunicator() = default;
    virtual bool open() = 0;
    virtual bool close() = 0;
    virtual int read(unsigned char* buffer, int size) = 0;
    virtual int write(const unsigned char* data, int size) = 0;
    virtual bool isConnected() const = 0;
};

2.2 具体实现类

// 串口实现
class SerialCommunicator : public IDataCommunicator {
public:
    SerialCommunicator(const std::string& port, int baudrate)
        : port_(port), baudrate_(baudrate), is_open_(false) {}

    bool open() override {
        std::cout << "Opening serial port " << port_ << " at " << baudrate_ << std::endl;
        // 实际串口打开逻辑...
        is_open_ = true;
        return true;
    }

    bool close() override {
        is_open_ = false;
        return true;
    }

    int read(unsigned char* buffer, int size) override {
        // 模拟读取
        return 10; // 返回字节数
    }

    int write(const unsigned char* data, int size) override {
        std::cout << "Writing " << size << " bytes via serial." << std::endl;
        return size;
    }

    bool isConnected() const override {
        return is_open_;
    }

private:
    std::string port_;
    int baudrate_;
    bool is_open_;
};

// TCP通信实现
class TcpCommunicator : public IDataCommunicator {
public:
    TcpCommunicator(const std::string& ip, int port)
        : ip_(ip), port_(port), connected_(false) {}

    bool open() override {
        std::cout << "Connecting to " << ip_ << ":" << port_ << std::endl;
        connected_ = true;
        return true;
    }

    bool close() override {
        connected_ = false;
        return true;
    }

    int read(unsigned char* buffer, int size) override {
        return 20;
    }

    int write(const unsigned char* data, int size) override {
        std::cout << "Sending " << size << " bytes over TCP." << std::endl;
        return size;
    }

    bool isConnected() const override {
        return connected_;
    }

private:
    std::string ip_;
    int port_;
    bool connected_;
};

2.3 高层模块使用接口

// 高层模块:设备管理器
class DeviceManager {
public:
    explicit DeviceManager(std::unique_ptr<IDataCommunicator> comm)
        : communicator_(std::move(comm)) {}

    void start() {
        if (communicator_->open()) {
            std::cout << "Device communication started." << std::endl;
        }
    }

    void sendData(const std::vector<unsigned char>& data) {
        communicator_->write(data.data(), data.size());
    }

    void pollData() {
        unsigned char buffer[256];
        int len = communicator_->read(buffer, 256);
        std::cout << "Received " << len << " bytes from device." << std::endl;
    }

    void stop() {
        communicator_->close();
    }

private:
    std::unique_ptr<IDataCommunicator> communicator_;
};

2.4 主函数中注入具体实现

int main() {
    // 可以自由切换通信方式,无需修改 DeviceManager
    
    // 使用串口
    auto serialComm = std::make_unique<SerialCommunicator>("COM3", 115200);
    DeviceManager device1(std::move(serialComm));
    device1.start();
    device1.sendData({0x01, 0x02, 0x03});
    device1.pollData();
    device1.stop();

    std::cout << "\n--- Switching to TCP ---\n" << std::endl;

    // 使用TCP
    auto tcpComm = std::make_unique<TcpCommunicator>("192.168.1.100", 502);
    DeviceManager device2(std::move(tcpComm));
    device2.start();
    device2.sendData({0xAA, 0xBB, 0xCC});
    device2.pollData();
    device2.stop();

    return 0;
}

三、优势总结

优势 说明
✅ 解耦 上位机逻辑与硬件通信解耦,更换通信方式不影响业务
✅ 可测试性 可以用 Mock 类模拟设备返回,便于单元测试
✅ 扩展性强 新增通信方式(如USB、CAN)只需新增实现类
✅ 维护性高 修改底层不影响上层逻辑

四、实际应用场景(工控 & 医疗上位机)

场景1:医疗设备上位机 —— 多种设备接入

需求背景:某医疗上位机需要连接血氧仪、心电仪、呼吸机等不同设备,它们可能通过串口、蓝牙或以太网连接。

解决方案:
class IMedicalDevice {
public:
    virtual ~IMedicalDevice() = default;
    virtual bool connect() = 0;
    virtual void disconnect() = 0;
    virtual std::vector<double> getRealTimeData() = 0;
    virtual std::string getDeviceID() = 0;
};

// 心电设备(通过串口)
class ECGDevice : public IMedicalDevice {
    std::unique_ptr<IDataCommunicator> comm_;
public:
    ECGDevice(std::unique_ptr<IDataCommunicator> c) : comm_(std::move(c)) {}
    bool connect() override { return comm_->open(); }
    void disconnect() override { comm_->close(); }
    std::vector<double> getRealTimeData() override {
        unsigned char buf[64];
        int len = comm_->read(buf, 64);
        // 解析心电数据...
        return {1.0, 1.1, 0.9}; // 示例
    }
    std::string getDeviceID() override { return "ECG-001"; }
};

// 血氧仪(通过蓝牙)
class SpO2Device : public IMedicalDevice {
    std::unique_ptr<IDataCommunicator> comm_;
public:
    SpO2Device(std::unique_ptr<IDataCommunicator> c) : comm_(std::move(c)) {}
    bool connect() override { return comm_->open(); }
    std::vector<double> getRealTimeData() override {
        return {98.6}; // 模拟血氧值
    }
    std::string getDeviceID() override { return "SPO2-002"; }
    void disconnect() override { comm_->close(); }
};

上位机主控系统可以统一管理所有设备:

class MedicalMonitor {
    std::vector<std::unique_ptr<IMedicalDevice>> devices_;

public:
    void addDevice(std::unique_ptr<IMedicalDevice> dev) {
        devices_.push_back(std::move(dev));
    }

    void run() {
        for (auto& dev : devices_) {
            dev->connect();
            auto data = dev->getRealTimeData();
            std::cout << "Device " << dev->getDeviceID() << " data: ";
            for (auto d : data) std::cout << d << " ";
            std::cout << std::endl;
        }
    }
};

✅ 优势:新增设备类型只需继承 IMedicalDevice,不影响现有代码。


场景2:工业控制上位机 —— PLC通信协议适配

需求背景:工厂中使用不同品牌的PLC(如西门子、三菱、欧姆龙),通信协议不同(Modbus TCP、S7、EtherNet/IP)。

设计:
class IPlcCommunicator {
public:
    virtual ~IPlcCommunicator() = default;
    virtual bool writeRegister(int addr, int value) = 0;
    virtual int readRegister(int addr) = 0;
    virtual bool connect() = 0;
};

class ModbusPlc : public IPlcCommunicator {
    std::unique_ptr<IDataCommunicator> transport_; // TCP或串口
public:
    ModbusPlc(std::unique_ptr<IDataCommunicator> t) : transport_(std::move(t)) {}
    bool connect() override { return transport_->open(); }
    bool writeRegister(int addr, int value) override {
        std::cout << "Modbus: Write " << value << " to address " << addr << std::endl;
        return true;
    }
    int readRegister(int addr) override {
        std::cout << "Modbus: Read from " << addr << std::endl;
        return 100;
    }
};

class S7Plc : public IPlcCommunicator {
public:
    bool connect() override { /* S7协议连接 */ return true; }
    bool writeRegister(int addr, int value) override {
        std::cout << "S7: Write " << value << " to DB" << addr << std::endl;
        return true;
    }
    int readRegister(int addr) override {
        return 200;
    }
};

上位机控制逻辑:

class PlcController {
    std::unique_ptr<IPlcCommunicator> plc_;

public:
    PlcController(std::unique_ptr<IPlcCommunicator> p) : plc_(std::move(p)) {}

    void startCycle() {
        plc_->connect();
        int val = plc_->readRegister(100);
        plc_->writeRegister(200, val + 1);
    }
};

✅ 可灵活替换PLC类型,配置驱动即可,无需重写控制逻辑。


五、进阶技巧

5.1 工厂模式 + 配置文件动态创建

std::unique_ptr<IDataCommunicator> createCommunicator(const std::string& type) {
    if (type == "serial") {
        return std::make_unique<SerialCommunicator>("COM3", 115200);
    } else if (type == "tcp") {
        return std::make_unique<TcpCommunicator>("192.168.1.10", 502);
    }
    throw std::invalid_argument("Unknown communicator type");
}

结合XML/JSON配置,实现运行时切换通信方式。

5.2 使用依赖注入容器(轻量级DI)

虽然C++没有像Java Spring那样的框架,但可以用简单工厂+注册机制模拟:

class ServiceLocator {
    static std::map<std::string, std::function<std::unique_ptr<void>()>> services;

public:
    template<typename T>
    static void registerService(const std::string& name, std::function<std::unique_ptr<T>()> factory) {
        services[name] = [factory]() -> std::unique_ptr<void> {
            return std::unique_ptr<void>(factory().release());
        };
    }

    template<typename T>
    static std::unique_ptr<T> getService(const std::string& name) {
        auto it = services.find(name);
        if (it != services.end()) {
            auto ptr = std::unique_ptr<T>(static_cast<T*>(it->second().get()));
            return ptr;
        }
        return nullptr;
    }
};

六、总结

项目 说明
核心思想 高层不依赖低层,都依赖抽象
实现方式 抽象基类 + 多态 + 智能指针 + 依赖注入
适用场景 工控、医疗、嵌入式等需要硬件适配的系统
关键好处 易扩展、易测试、低耦合、高内聚
推荐实践 接口独立头文件、避免循环依赖、使用工厂模式

通过“依赖接口编程”,你可以构建出稳定、可维护、可扩展的上位机系统,特别适合长期演进的工业和医疗项目。建议在项目初期就规划好核心接口,后期扩展将事半功倍。

代码

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐