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++ 的多继承机制,可以构建出高内聚、低耦合、易扩展的软件架构。