面向对象设计原则——C++

第 1 课:面向对象设计原则

1. 为什么需要设计原则?

假设你正在开发一个仪器控制程序,既要控制 Keithley 电源,又要控制红外相机和温控设备。

最开始可能写成这样:

cpp 复制代码
class DeviceController
{
public:
    void start(int type)
    {
        if (type == 0)
        {
            // 启动电源
        }
        else if (type == 1)
        {
            // 启动相机
        }
        else if (type == 2)
        {
            // 启动温控器
        }
    }
};

设备少的时候没问题,但如果后续增加激光器、运动控制器、其他型号的电源,就需要不断修改这个类。

可能出现的问题:

  • 一个类负责太多事情。
  • 新增设备需要修改已有代码。
  • 更换某个设备时影响其他模块。
  • 单元测试困难。
  • 业务逻辑与具体硬件绑定。
    面向对象设计原则,就是帮助我们系统性地解决这些问题。
    2. 单一职责原则(SRP)
    一个类应该只有一个主要的变化原因。

例如,把设备通信和设备控制全部放进同一个类:

cpp 复制代码
class PowerDevice
{
public:
    void connectDevice()
    {
        // TCP 连接
    }

    void sendCommand()
    {
        // 发送仪器指令
    }

    void setVoltage(double voltage)
    {
        // 设置电压
    }

    void saveConfig()
    {
        // 保存配置
    }
};

这个类同时负责通信、业务控制和配置存储。任何一个职责变化,都可能要求修改它。

更合理的设计是按职责拆分:

cpp 复制代码
class PowerCommunication
{
public:
    void connectDevice();
    void sendCommand(const QByteArray& data);
};

class PowerController
{
public:
    void setVoltage(double voltage);
};

class PowerConfig
{
public:
    void saveConfig();
};

职责关系:

PowerController

负责电源业务控制

PowerCommunication

负责底层通信

PowerConfig

负责配置读写,可由控制层调用

注意:并不是类拆得越多越好。只有当职责确实独立、变化原因不同或需要独立测试时,拆分才有意义。

3. 开闭原则(OCP)

对扩展开放,对修改关闭。

意思是:增加新功能时,尽量通过新增实现完成,而不是不断修改稳定的核心代码。

cpp 复制代码
先定义统一接口:
class Device
{
public:
    virtual ~Device() = default;
    virtual void start() = 0;
};


具体设备分别实现接口:
class PowerDevice : public Device
{
public:
    void start() override
    {
        qDebug() << "启动电源";
    }
};

class CameraDevice : public Device
{
public:
    void start() override
    {
        qDebug() << "启动相机";
    }
};


控制器只依赖 Device:
class DeviceController
{
public:
    void startDevice(Device* device)
    {
        if (device)
            device->start();
    }
};


使用:
PowerDevice power;
CameraDevice camera;

DeviceController controller;
controller.startDevice(&power);
controller.startDevice(&camera);


以后新增温控设备,只需要实现 Device 接口:
class TemperatureDevice : public Device
{
public:
    void start() override
    {
        qDebug() << "启动温控器";
    }
};


DeviceController 不需要修改。

这就是多态与开闭原则结合的典型应用。

4. 依赖倒置原则(DIP)

这是我最建议你深入掌握的原则。

核心思想:

  • 高层业务模块不应该依赖底层具体实现。
  • 两者都应该依赖抽象接口。
  • 抽象接口不应该依赖具体实现细节。
    例如,业务控制器直接创建具体设备:
cpp 复制代码
class PowerController
{
public:
    PowerController()
    {
        // 直接绑定具体型号
        m_device = new Power2657();
    }

private:
    Power2657* m_device = nullptr;
};

问题是,控制器被固定在 Power2657 上。换成 Power2470 或模拟设备进行测试时,就需要修改控制器。

改成接口依赖:

cpp 复制代码
class IPowerDevice
{
public:
    virtual ~IPowerDevice() = default;
    virtual bool setVoltage(double voltage) = 0;
};

class PowerController
{
public:
    explicit PowerController(IPowerDevice* device)
        : m_device(device) {}

    bool setVoltage(double voltage)
    {
        return m_device && m_device->setVoltage(voltage);
    }

private:
    IPowerDevice* m_device; // 非拥有指针
};

真实电源和模拟电源都可以实现 IPowerDevice,控制器无需知道具体型号。

这里的 m_device 是非拥有指针,必须保证被注入的对象在控制器使用期间一直有效。

5. 组合优于继承

继承表示一种类型关系,组合表示一个对象使用或拥有另一个对象。

例如,不建议为了复用通信功能,让所有设备都继承某个具体 TCP 客户端:

cpp 复制代码
class Camera : public TcpClient
{
};

class Power : public TcpClient
{
};

因为相机和电源并不是 TCP 客户端,它们只是可能通过 TCP 通信。

更合理的结构:

cpp 复制代码
class TcpClient
{
public:
    bool connectToDevice();
    bool sendData(const QByteArray& data);
};

class Camera
{
public:
    explicit Camera(TcpClient* client)
        : m_client(client) {}

private:
    TcpClient* m_client;
};

这样相机可以使用通信对象,而不必继承其所有接口。

实际项目中,如果设备需要切换 TCP、串口或模拟通信实现,可以进一步组合一个抽象通信接口,而不是绑定具体的 TcpClient。

本课小结

原则 要解决的问题

单一职责 SRP 一个类承担太多职责

开闭原则 OCP 新增功能总要修改旧代码

依赖倒置 DIP 业务代码依赖具体设备

组合优于继承 继承关系不合理、耦合过强

接下来建议学习依赖注入(DI)与控制反转(IoC),把今天的原则转化成完整的工程代码,包括工厂如何创建设备、业务控制器如何接收设备,以及 Qt 项目中如何管理这些对象的生命周期。

相关推荐
Java后端的Ai之路28 分钟前
RAG 还是 Agent 自主检索?Claude Code 删掉向量库之后,我把选型讲透了
开发语言·人工智能·prompt·agent·rag
starzy199039 分钟前
深入理解Scala高阶函数:函数式编程核心
开发语言·scala
科技苑6 小时前
C++ CAS与ABA问题详解,无锁编程的经典陷阱怎么解决?
c++·人工智能
czq_26867194877 小时前
Python打卡第46天
开发语言·python
Evand J9 小时前
【MATLAB例程介绍】(17)永磁同步电机(PMSM)基于自适应无迹卡尔曼滤波(AUKF)的电流噪声在线估计。测量噪声协方差自适应与转速估计对比仿真
开发语言·matlab·卡尔曼滤波·无迹卡尔曼滤波·pmsm·电机滤波
BaiPeter10 小时前
ZPL文件预览
开发语言·c++
悠仁さん13 小时前
【C++】哈希表
c++·算法·散列表
Severus_black15 小时前
【C++初阶】list使用及相关实现,思路顺滑流畅!
c语言·数据结构·c++·stl·list
噢,我明白了15 小时前
JAVA中的一些基础应用知识点
java·开发语言
时间的拾荒人16 小时前
Redis 入门详解:核心特性、重要文件与全局命令实战
开发语言·redis·面试