嵌入式Linux C++常用设计模式

嵌入式Linux C++常用设计模式

1. 前言

1.1 为什么嵌入式需要设计模式?

嵌入式开发面临硬件唯一、驱动迭代频繁、状态复杂、异步事件多、资源受限等问题,设计模式并非单纯的代码技巧,而是解耦硬件与业务、简化复杂逻辑、提升代码可移植性的工程化方案,让嵌入式代码从能用走向好用、易维护。

嵌入式痛点 设计模式的解决思路
硬件资源唯一(如I2C控制器、串口) 单例模式确保资源唯一控制,避免竞争冲突
硬件/驱动迭代频繁(更换传感器/芯片) 适配器/工厂模式解耦业务与底层,最小化代码修改
系统状态复杂(待机/工作/故障/低功耗) 状态模式替代臃肿switch-case,提升可读性与扩展性
异步事件多(按键中断、传感器报警、IO触发) 观察者模式实现解耦的事件驱动,简化异步逻辑
内存/算力受限(嵌入式通用痛点) 轻量级设计模式规范代码结构,避免冗余与内存泄漏

1.2 设计模式三原则

设计模式的核心是基于原则的灵活运用,以下为设计模式三原则:

原则 核心定义 示例
单一职责 一个类只负责一个独立的功能模块,不做跨界操作 I2C驱动类仅处理总线读写,不包含传感器数据解析;传感器类仅处理数据采集,不包含业务逻辑
开闭原则 扩展开放 ,对修改关闭 新增传感器时,继承基类实现新方法,不修改原有驱动框架;新增通信协议时,新增策略类,不修改通信管理器
依赖倒置 面向接口/抽象类编程,而非具体实现 定义Sensor抽象接口,所有传感器驱动实现该接口,业务层仅调用接口方法,不依赖具体传感器

2. 创建型模式(核心解决:谁来初始化硬件/资源?)

创建型模式的核心是控制对象的创建方式 ,适配嵌入式中"硬件资源唯一、外设类型多变"的特点,重点掌握单例模式(饿汉+懒汉)、工厂模式(简单+工厂方法)

2.1 单例模式

核心介绍

嵌入式中大量物理资源是全局唯一 的(如I2C/SPI控制器、系统日志器、配置管理器),单例模式确保一个类在程序生命周期内仅创建一个实例,避免多实例操作硬件导致的资源竞争、寄存器混乱等问题。

单例分为两种核心实现,核心差异是初始化时机,需根据资源类型选择,同时重点关注:线程安全、内存占用、ISR(中断服务函数)兼容性。

  • 饿汉式:程序启动时立即初始化实例(空间换时间)
  • 懒汉式:首次使用时才初始化实例(时间换空间)

(1)饿汉式单例

核心特性

程序启动时(类加载阶段)完成实例初始化,天生线程安全 (无需加锁),但会提前占用静态内存;支持在ISR中调用 (无锁操作,不会导致中断阻塞),适合启动后必须立即初始化的核心硬件资源(如系统日志器、核心总线控制器、中断管理器)。

代码示例(饿汉式I2C控制器)
cpp 复制代码
#include <iostream>
#include <unistd.h>   
#include <fcntl.h>    
#include <stdint.h>   

// 嵌入式I2C控制器(饿汉式单例)
class I2CController_Hungry {
private:
    // 1. 私有构造函数:禁止外部实例化
    I2CController_Hungry() {
        // 打开I2C设备节点、初始化总线参数
        fd_ = open("/dev/i2c-1", O_RDWR);
        if (fd_ < 0) {
            std::cerr << "[饿汉式] I2C控制器初始化失败" << std::endl;
            return;
        }
        std::cout << "[饿汉式] I2C控制器初始化成功(程序启动时执行)" << std::endl;
    }

    // 2. 禁用拷贝/赋值运算符:防止多实例创建
    I2CController_Hungry(const I2CController_Hungry&) = delete;
    I2CController_Hungry& operator=(const I2CController_Hungry&) = delete;

    // 3. 静态成员实例:程序启动时直接初始化(饿汉式核心)
    static I2CController_Hungry instance_;
    int fd_; // I2C设备文件描述符(嵌入式核心资源)

public:
    // 4. 公有静态方法:获取全局唯一实例(无锁,天生线程安全)
    static I2CController_Hungry& getInstance() {
        return instance_;
    }

    // I2C总线写操作(寄存器+数据)
    bool writeReg(uint8_t dev_addr, uint8_t reg_addr, uint8_t data) {
        if (fd_ < 0) return false;
        // 通过ioctl实现I2C写(此处简化)
        std::cout << "[饿汉式] I2C写:设备0x" << std::hex << (int)dev_addr
                  << " 寄存器0x" << (int)reg_addr << " 数据0x" << (int)data << std::dec << std::endl;
        return true;
    }

    // 5. 资源释放方法
    static void release() {
        if (instance_.fd_ >= 0) {
            close(instance_.fd_);
            instance_.fd_ = -1;
            std::cout << "[饿汉式] I2C控制器资源释放完成" << std::endl;
        }
    }
};

// 静态实例初始化:程序启动时执行(饿汉式核心,必须写在类外)
I2CController_Hungry I2CController_Hungry::instance_;

// 饿汉式单例测试函数
void test_hungry_singleton() {
    std::cout << "\n===== 饿汉式单例测试 =====" << std::endl;
    // 获取实例(多次调用返回同一个)
    I2CController_Hungry& i2c1 = I2CController_Hungry::getInstance();
    i2c1.writeReg(0x48, 0x00, 0x20); // 操作温湿度传感器

    I2CController_Hungry& i2c2 = I2CController_Hungry::getInstance();
    std::cout << "是否为同一实例:" << (&i2c1 == &i2c2) << std::endl; // 输出1(真)

    I2CController_Hungry::release();
}

(2)懒汉式单例

核心特性

首次调用getInstance()时才完成实例初始化,按需占用内存 (嵌入式内存受限场景的首选),但需手动实现线程安全(双重检查锁);不支持在ISR中调用 (加锁操作会导致中断阻塞),适合非启动必用的非核心资源(如传感器控制器、扩展模块、日志辅助器)。

代码示例(线程安全懒汉式I2C控制器)
cpp 复制代码
#include <iostream>
#include <mutex>    
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>

// 嵌入式I2C控制器(懒汉式单例,线程安全)
class I2CController_Lazy {
private:
    // 1. 私有构造函数
    I2CController_Lazy() {
        fd_ = open("/dev/i2c-1", O_RDWR);
        if (fd_ < 0) {
            std::cerr << "[懒汉式] I2C控制器初始化失败" << std::endl;
            return;
        }
        std::cout << "[懒汉式] I2C控制器初始化成功(首次使用时执行)" << std::endl;
    }

    // 2. 禁用拷贝/赋值
    I2CController_Lazy(const I2CController_Lazy&) = delete;
    I2CController_Lazy& operator=(const I2CController_Lazy&) = delete;

    // 3. 静态实例指针:初始为nullptr(懒汉式核心)
    static I2CController_Lazy* instance_;
    // 4. 静态互斥锁:保证线程安全
    static std::mutex mutex_;
    int fd_;

public:
    // 5. 公有静态方法:获取实例(双重检查锁,线程安全+低开销)
    static I2CController_Lazy* getInstance() {
        if (instance_ == nullptr) { // 第一层检查:减少锁竞争(99%场景直接跳过锁)
            std::lock_guard<std::mutex> lock(mutex_); // 加锁:保证多线程安全
            if (instance_ == nullptr) { // 第二层检查:确保唯一实例
                instance_ = new I2CController_Lazy();
            }
        }
        return instance_;
    }

    // I2C总线写操作
    bool writeReg(uint8_t dev_addr, uint8_t reg_addr, uint8_t data) {
        if (fd_ < 0 || instance_ == nullptr) return false;
        std::cout << "[懒汉式] I2C写:设备0x" << std::hex << (int)dev_addr
                  << " 寄存器0x" << (int)reg_addr << " 数据0x" << (int)data << std::dec << std::endl;
        return true;
    }

    // 6. 资源释放
    static void release() {
        std::lock_guard<std::mutex> lock(mutex_); // 加锁释放,避免多线程冲突
        if (instance_ != nullptr) {
            if (instance_->fd_ >= 0) close(instance_->fd_);
            delete instance_;
            instance_ = nullptr; // 重置为nullptr,支持重新初始化
            std::cout << "[懒汉式] I2C控制器资源释放完成" << std::endl;
        }
    }
};

// 静态成员初始化:初始为nullptr(懒汉式核心)
I2CController_Lazy* I2CController_Lazy::instance_ = nullptr;
std::mutex I2CController_Lazy::mutex_;

// 懒汉式单例测试函数
void test_lazy_singleton() {
    std::cout << "\n===== 懒汉式单例测试 =====" << std::endl;
    // 首次调用:初始化实例
    I2CController_Lazy* i2c1 = I2CController_Lazy::getInstance();
    i2c1->writeReg(0x48, 0x01, 0x30);

    // 再次调用:直接返回已有实例
    I2CController_Lazy* i2c2 = I2CController_Lazy::getInstance();
    std::cout << "是否为同一实例:" << (i2c1 == i2c2) << std::endl; // 输出1(真)

    I2CController_Lazy::release();
}

(3)饿汉式 vs 懒汉式

特性 饿汉式单例 懒汉式单例(线程安全版)
初始化时机 程序启动时(类加载阶段) 首次调用getInstance()
线程安全 安全(无需锁,无开销) 需双重检查锁(C++11+支持,低开销)
内存占用 启动即占静态内存 按需占用(嵌入式内存受限首选)
ISR兼容性 完全支持(无锁,不阻塞中断) 完全不支持(锁会导致系统崩溃)
实现复杂度 简单(无额外逻辑) 中等(双重检查锁+锁管理)
资源释放 静态实例,仅需释放硬件资源 动态实例,需释放内存+硬件资源
嵌入式适用场景 核心资源:系统日志、核心总线、中断管理器 非核心资源:传感器、扩展模块、辅助工具
核心优点 简单、无锁开销、ISR兼容、启动即可用 节省内存、按需初始化、支持重新初始化
核心缺点 浪费内存、启动时间略长 实现复杂、不支持ISR、有轻微锁开销

(4)实践选择

  1. 资源分级选择:核心硬件用饿汉式,非核心硬件用懒汉式,兼顾性能与内存;
  2. 必须手动释放 :嵌入式无垃圾回收(GC),所有单例必须实现release()方法,释放文件句柄、寄存器、内存等资源;
  3. 禁用ISR调用懒汉式:中断服务函数中禁止调用加锁的代码,否则会导致系统死锁;
  4. C++11+必选:保证双重检查锁的内存模型原子性,避免多线程下的半初始化问题;
  5. 禁用裸指针传递 :懒汉式返回指针后,避免外部手动delete,统一通过release()释放。

2.2 工厂模式

核心介绍

嵌入式开发中常遇到同类型外设多型号适配 的问题(如同时兼容DHT11/BME280温湿度传感器、WiFi/LoRa/4G通信模块),工厂模式将对象的创建逻辑与业务逻辑解耦,业务层无需关注具体硬件的实现细节,仅通过工厂获取实例即可。

工厂模式分为两个层级,核心差异是是否符合开闭原则 ,需根据嵌入式系统的规模和扩展需求选择:

  • 简单工厂模式:一个工厂类创建所有类型实例(简单但违反开闭原则);
  • 工厂方法模式:一个产品类型对应一个工厂类(符合开闭原则,扩展友好)。

(1)简单工厂模式

核心特性

一个工厂类 根据传入的参数(类型字符串/枚举)创建不同的产品实例,代码极简、类数量少、内存占用低,适合传感器/外设类型固定、无需频繁扩展小规模嵌入式系统

缺点是违反开闭原则 :新增产品时需修改工厂类的create方法,适合产品类型稳定的场景。

代码示例(传感器简单工厂)
cpp 复制代码
#include <iostream>
#include <string>
#include <memory>   
#include <stdint.h>

// 抽象产品:传感器接口(依赖倒置原则,所有传感器实现此接口)
class Sensor {
public:
    virtual ~Sensor() = default;
    virtual float readData() = 0;    // 纯虚函数:统一数据读取接口
    virtual std::string getType() = 0; // 纯虚函数:获取传感器类型
};

// 具体产品1:温湿度传感器(DHT11)
class TemperatureSensor : public Sensor {
public:
    float readData() override {
        // 从I2C/SPI读取数据(此处模拟)
        return 25.6f; // 单位:℃
    }
    std::string getType() override {
        return "TemperatureSensor(DHT11)";
    }
};

// 具体产品2:光照传感器(BH1750)
class LightSensor : public Sensor {
public:
    float readData() override {
        return 800.5f; // 单位:lux(勒克斯)
    }
    std::string getType() override {
        return "LightSensor(BH1750)";
    }
};

// 简单工厂类:核心(一个工厂创建所有传感器实例)
class SensorSimpleFactory {
public:
    // 静态创建方法:根据类型创建实例,返回智能指针
    static std::unique_ptr<Sensor> createSensor(const std::string& type) {
        if (type == "temp") {
            return std::make_unique<TemperatureSensor>();
        } else if (type == "light") {
            return std::make_unique<LightSensor>();
        } else {
            std::cerr << "[简单工厂] 不支持的传感器类型:" << type << std::endl;
            return nullptr;
        }
    }
};

// 简单工厂测试函数
void test_simple_factory() {
    std::cout << "\n===== 简单工厂模式测试 =====" << std::endl;
    // 从配置文件/硬件ID读取传感器类型
    std::string sensor_type = "temp";
    auto sensor1 = SensorSimpleFactory::createSensor(sensor_type);
    if (sensor1) {
        std::cout << "传感器类型:" << sensor1->getType()
                  << " 读取值:" << sensor1->readData() << "℃" << std::endl;
    }

    sensor_type = "light";
    auto sensor2 = SensorSimpleFactory::createSensor(sensor_type);
    if (sensor2) {
        std::cout << "传感器类型:" << sensor2->getType()
                  << " 读取值:" << sensor2->readData() << "lux" << std::endl;
    }

    // 新增传感器需修改工厂类(违反开闭原则)
    // auto sensor3 = SensorSimpleFactory::createSensor("pressure"); // 返回nullptr
}

(2)工厂方法模式

核心特性

工厂抽象为接口 ,定义创建产品的纯虚方法,每个产品类型对应一个具体工厂类 ,新增产品时仅需新增"产品类+工厂类",无需修改原有代码,完全符合开闭原则 ,适合传感器/外设类型需频繁扩展中大规模嵌入式系统(如工业网关、智能硬件平台、车载终端)。

缺点是类数量会随产品类型增加而增多,需注意内存占用,但相较于扩展带来的维护成本,此缺点可忽略。

代码示例(传感器工厂方法)
cpp 复制代码
#include <iostream>
#include <string>
#include <memory>
#include <stdint.h>

// 复用抽象产品:Sensor接口(与简单工厂一致)
class Sensor {
public:
    virtual ~Sensor() = default;
    virtual float readData() = 0;
    virtual std::string getType() = 0;
};

// 复用具体产品:TemperatureSensor/LightSensor(与简单工厂一致)
class TemperatureSensor : public Sensor {
public:
    float readData() override { return 25.6f; }
    std::string getType() override { return "TemperatureSensor(DHT11)"; }
};
class LightSensor : public Sensor {
public:
    float readData() override { return 800.5f; }
    std::string getType() override { return "LightSensor(BH1750)"; }
};

// 抽象工厂:传感器工厂接口(工厂方法核心,定义创建产品的纯虚方法)
class SensorFactory {
public:
    virtual ~SensorFactory() = default;
    virtual std::unique_ptr<Sensor> createSensor() = 0; // 工厂方法
};

// 具体工厂1:温湿度传感器工厂(对应TemperatureSensor)
class TemperatureSensorFactory : public SensorFactory {
public:
    std::unique_ptr<Sensor> createSensor() override {
        return std::make_unique<TemperatureSensor>();
    }
};

// 具体工厂2:光照传感器工厂(对应LightSensor)
class LightSensorFactory : public SensorFactory {
public:
    std::unique_ptr<Sensor> createSensor() override {
        return std::make_unique<LightSensor>();
    }
};

// 新增产品:气压传感器(BMP280)------ 无需修改原有代码(符合开闭原则)
class PressureSensor : public Sensor {
public:
    float readData() override { return 101.325f; } // 单位:kPa
    std::string getType() override { return "PressureSensor(BMP280)"; }
};

// 新增工厂:气压传感器工厂 ------ 仅新增类,无任何修改操作
class PressureSensorFactory : public SensorFactory {
public:
    std::unique_ptr<Sensor> createSensor() override {
        return std::make_unique<PressureSensor>();
    }
};

// 工厂方法测试函数
void test_factory_method() {
    std::cout << "\n===== 工厂方法模式测试 =====" << std::endl;
    // 方式1:直接创建具体工厂(嵌入式真实场景:从配置动态选择)
    std::unique_ptr<SensorFactory> temp_factory = std::make_unique<TemperatureSensorFactory>();
    auto temp_sensor = temp_factory->createSensor();
    std::cout << "传感器类型:" << temp_sensor->getType()
              << " 读取值:" << temp_sensor->readData() << "℃" << std::endl;

    // 方式2:新增气压传感器(仅新增类,原有代码无任何修改)
    std::unique_ptr<SensorFactory> pressure_factory = std::make_unique<PressureSensorFactory>();
    auto pressure_sensor = pressure_factory->createSensor();
    std::cout << "传感器类型:" << pressure_sensor->getType()
              << " 读取值:" << pressure_sensor->readData() << "kPa" << std::endl;
}

(3)简单工厂 vs 工厂方法

特性 简单工厂模式 工厂方法模式
核心结构 1个工厂类 + N个产品类 1个抽象工厂 + N个具体工厂 + N个产品类
开闭原则 违反(新增产品需修改工厂类) 符合(新增产品仅需新增类)
代码复杂度 极低(嵌入式入门友好) 中等(类数量增多,逻辑清晰)
内存占用 极低(少类,少静态资源) 中等(多类,可接受)
扩展成本 高(修改原有代码,易引入bug) 低(仅新增类,无修改操作)
维护成本 高(产品越多,工厂方法越臃肿) 低(产品与工厂一一对应,易维护)
嵌入式适用场景 小规模系统、产品类型固定 中大规模系统、产品类型需频繁扩展
核心优点 简单、易实现、内存占用少、开发快 符合开闭原则、扩展灵活、易维护、可测试
核心缺点 扩展需修改代码、维护成本高 类数量多、初始化略复杂、轻微内存增加

(4)嵌入式工厂模式选择

  1. 按系统规模选择:单片机Linux/小型设备用简单工厂,工业网关/车载终端用工厂方法;
  2. 结合配置文件 :将外设类型写入配置文件(如/etc/periph.conf),程序启动时动态读取并创建工厂/产品,提升灵活性;
  3. 优先使用智能指针 :用std::unique_ptr管理工厂和产品实例,嵌入式避免裸指针泄漏;
  4. 产品接口归一化:抽象产品接口的方法需足够通用,避免因产品差异导致接口臃肿;
  5. 平滑迁移:先通过简单工厂快速实现功能,后续若需扩展,再平滑迁移到工厂方法模式(接口完全兼容)。

3. 结构性模式(核心解决:如何组织类以适配底层接口)

结构性模式的核心是通过类的组合/继承,构建灵活的类结构 ,解决嵌入式中"底层接口不兼容、子系统逻辑复杂、跨进程访问硬件"的问题,重点掌握适配器模式、外观模式、代理模式,均为嵌入式开发中的高频使用模式。

3.1 适配器模式

核心介绍

嵌入式开发中常遇到接口不兼容的问题:第三方驱动/旧版代码的接口与当前系统的标准接口不一致、更换芯片后外设接口发生变化、使用开源库时接口与业务逻辑不匹配。

适配器模式将不兼容的接口转换为系统的标准接口 ,让原本因接口不兼容而无法协作的类能够正常工作,完全解耦业务逻辑与底层驱动,更换硬件/库时仅需修改适配器,无需修改业务代码。

嵌入式中优先使用对象适配器(组合适配者,而非继承),更灵活、更节省内存。

代码示例(串口驱动适配器)
cpp 复制代码
#include <iostream>
#include <string>
#include <stdint.h>

// 目标接口:系统标准串口接口(当前系统的统一接口)
class SerialPort {
public:
    virtual ~SerialPort() = default;
    virtual bool openPort(int baud_rate) = 0; // 标准打开接口:仅需波特率
    virtual bool sendData(const std::string& data) = 0; // 标准发送接口:字符串
};

// 适配者:第三方旧串口驱动(接口不兼容,无法直接使用)
class OldSerialDriver {
public:
    // 旧接口:参数多,无返回值
    void initSerial(int rate, const std::string& port, uint8_t parity) {
        std::cout << "旧驱动初始化:端口" << port << " 波特率" << rate << " 校验位" << (int)parity << std::endl;
        is_init_ = true;
    }
    // 旧接口:数据为char*+长度,与标准接口不一致
    void writeData(const char* buf, int len) {
        if (!is_init_) return;
        std::cout << "旧驱动发送:" << std::string(buf, len) << std::endl;
    }
private:
    bool is_init_ = false;
};

// 适配器:将旧驱动接口适配为系统标准接口(对象适配器,组合适配者)
class SerialAdapter : public SerialPort {
public:
    SerialAdapter() : old_driver_() {}
    // 适配标准openPort到旧initSerial
    bool openPort(int baud_rate) override {
        old_driver_.initSerial(baud_rate, "/dev/ttyS0", 0); // 嵌入式串口节点,固定校验位
        return true;
    }
    // 适配标准sendData到旧writeData
    bool sendData(const std::string& data) override {
        old_driver_.writeData(data.c_str(), data.length());
        return true;
    }
private:
    OldSerialDriver old_driver_; // 组合适配者(对象适配器核心)
};

// 业务层:仅依赖标准接口,无需关注底层驱动实现
void sendDeviceConfig(SerialPort& serial) {
    serial.openPort(9600);
    serial.sendData("device_config: baud=9600, parity=0, data=8");
}

// 测试函数
void test_adapter() {
    std::cout << "\n===== 适配器模式测试 =====" << std::endl;
    SerialAdapter serial_adapter;
    sendDeviceConfig(serial_adapter); // 业务层无感知,适配层完成接口转换
}
应用要点
  1. 优先对象适配器:通过组合实现,而非继承,避免类层级过深,节省内存;
  2. 隔离所有修改:第三方库/旧驱动的所有适配逻辑都放在适配器中,更换库时仅需修改适配器;
  3. 轻量化适配:适配器层仅做接口转换,不做复杂业务逻辑,避免影响嵌入式实时性;
  4. 硬件接口适配:更换芯片时,适配器层屏蔽硬件寄存器的差异,业务层无需修改。

3.2 外观模式

核心介绍

嵌入式中很多子系统逻辑复杂:网络协议栈(IP/TCP/UDP初始化)、硬件通信模块(蓝牙/WiFi的扫描/连接/发送)、文件系统(挂载/打开/读写/关闭),这些子系统包含多个相互关联的类,直接调用会让业务代码臃肿、难以维护。

外观模式为复杂的子系统提供一个统一的简单接口 ,屏蔽底层子系统的细节,业务层仅需调用外观类的方法,无需关注子系统的内部实现,大幅简化代码调用。

代码示例(网络子系统外观)
cpp 复制代码
#include <iostream>
#include <string>

// 子系统1:网络协议栈初始化(复杂的底层操作)
class NetworkStack {
public:
    bool init() {
        std::cout << "网络协议栈初始化:IP=192.168.1.100, Gateway=192.168.1.1" << std::endl;
        return true;
    }
    void deinit() {
        std::cout << "网络协议栈资源释放" << std::endl;
    }
};

// 子系统2:TCP连接管理(复杂的连接逻辑)
class TCPHandler {
public:
    bool connectServer(const std::string& ip, int port) {
        std::cout << "TCP连接:" << ip << ":" << port << " 连接成功" << std::endl;
        return true;
    }
};

// 子系统3:UDP数据发送(复杂的数据包封装)
class UDPSender {
public:
    bool sendPacket(const std::string& data) {
        std::cout << "UDP数据包发送:" << data << std::endl;
        return true;
    }
};

// 外观类:网络管理器(封装所有子系统,提供简单接口)
class NetworkManager {
public:
    NetworkManager() : stack_(), tcp_(), udp_() {}
    // 统一的发送方法:封装子系统的所有操作
    bool sendNetworkData(const std::string& ip, int port, const std::string& data) {
        if (!stack_.init()) return false;
        if (!tcp_.connectServer(ip, port)) return false;
        return udp_.sendPacket(data);
    }
    ~NetworkManager() {
        stack_.deinit(); // 统一释放子系统资源
    }
private:
    NetworkStack stack_; // 组合所有子系统
    TCPHandler tcp_;
    UDPSender udp_;
};

// 测试函数
void test_facade() {
    std::cout << "\n===== 外观模式测试 =====" << std::endl;
    NetworkManager net_mgr;
    // 业务层仅需调用一个方法,无需关注子系统细节
    net_mgr.sendNetworkData("192.168.1.1", 8080, "embedded_linux_data_123");
}
应用要点
  1. 统一资源管理:外观类统一初始化和释放子系统资源,避免嵌入式资源泄漏;
  2. 接口极简:外观类的方法需贴合业务逻辑,避免暴露底层子系统的细节;
  3. 子系统解耦:外观类是子系统与业务层的唯一交互入口,子系统内部修改不影响业务层;
  4. 实时性保障:外观类仅做方法转发,不做复杂逻辑,避免影响嵌入式实时性。

3.3 代理模式(IPC代理为主)

核心介绍

嵌入式Linux中多进程开发是常态:特权进程管理硬件、普通进程处理业务、应用进程提供交互,跨进程访问硬件/服务是核心需求(如通过普通进程操作硬件、通过应用进程调用特权进程的服务)。

代理模式为远程/复杂对象提供一个本地代理对象 ,屏蔽跨进程通信(IPC)的细节,业务层调用代理对象的方法,代理对象通过IPC将请求转发给真实对象,处理结果后返回给业务层,实现透明的跨进程访问

嵌入式中常用的IPC方式:管道、共享内存、消息队列、Socket,代理模式可将这些IPC方式封装起来。

代码示例(管道IPC硬件代理)
cpp 复制代码
#include <iostream>
#include <string>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>

// 抽象服务:硬件服务接口(本地+远程统一接口)
class HardwareService {
public:
    virtual ~HardwareService() = default;
    virtual std::string getHardwareStatus() = 0; // 获取硬件状态
};

// 真实服务:硬件管理器(运行在特权子进程,直接操作硬件)
class RealHardwareService : public HardwareService {
public:
    std::string getHardwareStatus() override {
        // 读取CPU、内存、温度等硬件状态
        return "CPU:50%, MEM:30%, TEMP:45°C, VOLTAGE:3.3V";
    }
};

// 代理服务:硬件代理(运行在主进程,封装管道IPC,转发请求)
class HardwareProxy : public HardwareService {
public:
    HardwareProxy() {
        // 创建无名管道+子进程(嵌入式极简IPC实现)
        if (pipe(pipe_fd_) < 0) {
            std::cerr << "管道创建失败" << std::endl;
            return;
        }
        pid_t pid = fork();
        if (pid == 0) { // 子进程:运行真实硬件服务
            close(pipe_fd_[0]); // 关闭读端
            RealHardwareService real_hw;
            std::string status = real_hw.getHardwareStatus();
            // 将硬件状态写入管道
            write(pipe_fd_[1], status.c_str(), status.length());
            close(pipe_fd_[1]);
            exit(0);
        } else if (pid > 0) { // 主进程:作为代理
            close(pipe_fd_[1]); // 关闭写端
        }
    }

    // 代理方法:通过管道获取子进程的硬件状态
    std::string getHardwareStatus() override {
        if (pipe_fd_[0] < 0) return "获取硬件状态失败";
        char buf[128] = {0};
        read(pipe_fd_[0], buf, sizeof(buf)-1);
        close(pipe_fd_[0]);
        return std::string(buf);
    }

private:
    int pipe_fd_[2]; // 管道文件描述符(fd[0]读,fd[1]写)
};

// 测试函数
void test_proxy() {
    std::cout << "\n===== 代理模式测试 =====" << std::endl;
    HardwareProxy hw_proxy;
    // 业务层无感知IPC,直接调用代理方法
    std::cout << "硬件状态:" << hw_proxy.getHardwareStatus() << std::endl;
}
应用要点
  1. 屏蔽IPC细节:将管道/共享内存/D-Bus的实现封装在代理类中,业务层无需关注;
  2. 特权隔离:真实服务运行在特权进程,代理运行在普通进程,实现硬件访问的权限控制;
  3. 轻量化IPC:嵌入式优先使用管道/共享内存,避免D-Bus等重量级IPC的性能开销;
  4. 异常处理:代理类需处理IPC通信失败的情况,避免主进程崩溃。

4. 行为型模式(核心解决:硬件事件如何响应?任务逻辑流如何设计?)

行为型模式的核心是解决类与对象之间的交互和行为分配问题 ,适配嵌入式中异步事件多、状态机复杂、算法/协议需动态切换 的特点,重点掌握观察者模式、状态模式、策略模式,是嵌入式业务逻辑设计的核心模式。

4.1 观察者模式

核心介绍

嵌入式是事件驱动的系统:按键按下/松开、传感器阈值报警、IO口电平变化、网络数据到达,这些异步事件需要及时响应,且一个事件可能需要多个模块做出反应(如按键按下→LED亮+蜂鸣器响+数据记录)。

观察者模式实现事件源与观察者的解耦:事件源(如按键)维护一个观察者列表,当事件发生时,自动通知所有观察者,观察者接收到事件后执行自己的响应逻辑,新增/移除观察者无需修改事件源代码,符合开闭原则。

代码示例(按键事件观察者)
cpp 复制代码
#include <iostream>
#include <string>
#include <vector>
#include <stdint.h>

// 抽象观察者:所有事件响应者的统一接口
class Observer {
public:
    virtual ~Observer() = default;
    virtual void update(const std::string& event) = 0; // 事件更新方法
};

// 具体观察者1:LED灯(按键事件响应者)
class LEDObserver : public Observer {
public:
    void update(const std::string& event) override {
        if (event == "BUTTON_PRESS") {
            std::cout << "LED:亮红灯(按键按下)" << std::endl;
        } else if (event == "BUTTON_RELEASE") {
            std::cout << "LED:灭灯(按键松开)" << std::endl;
        }
    }
};

// 具体观察者2:蜂鸣器(按键事件响应者)
class BuzzerObserver : public Observer {
public:
    void update(const std::string& event) override {
        if (event == "BUTTON_PRESS") {
            std::cout << "蜂鸣器:短鸣一声(按键按下)" << std::endl;
        }
    }
};

// 主题(事件源):按键(维护观察者列表,触发事件时通知)
class ButtonSubject {
public:
    // 添加观察者
    void attach(Observer* obs) {
        observers_.push_back(obs);
    }
    // 移除观察者
    void detach(Observer* obs) {
        for (auto it = observers_.begin(); it != observers_.end(); ++it) {
            if (*it == obs) {
                observers_.erase(it);
                break;
            }
        }
    }
    // 通知所有观察者(事件触发核心)
    void notify(const std::string& event) {
        for (auto obs : observers_) {
            obs->update(event);
        }
    }
    // 模拟按键中断触发(嵌入式真实场景:ISR中调用此方法)
    void onButtonPress() {
        notify("BUTTON_PRESS");
    }
    void onButtonRelease() {
        notify("BUTTON_RELEASE");
    }
private:
    std::vector<Observer*> observers_; // 观察者列表
};

// 测试函数
void test_observer() {
    std::cout << "\n===== 观察者模式测试 =====" << std::endl;
    ButtonSubject button;
    LEDObserver led;
    BuzzerObserver buzzer;

    // 注册观察者
    button.attach(&led);
    button.attach(&buzzer);

    // 模拟按键事件(嵌入式真实场景:中断服务函数触发)
    button.onButtonPress();
    button.onButtonRelease();

    // 移除蜂鸣器观察者
    button.detach(&buzzer);
    button.onButtonPress(); // 仅LED响应
}
应用要点
  1. 观察者轻量化update方法需极简,避免在ISR中执行耗时操作(可通过消息队列将逻辑放到主线程);
  2. 线程安全 :多线程下通知观察者需加锁(如std::mutex),避免数据竞争;
  3. 避免内存泄漏:移除观察者时需从列表中删除,避免野指针;
  4. 事件归一化:事件类型用枚举/字符串统一,便于扩展。

4.2 状态模式

核心介绍

嵌入式设备的核心逻辑往往是一个大型状态机 :设备初始化→待机→工作→故障→复位、传感器空闲→采集→处理→休眠,传统用switch-case实现的状态机,当状态/事件增多时,代码会极度臃肿、难以维护、易引入bug。

状态模式将每个状态封装为一个独立的类 ,每个状态类实现该状态下的事件处理逻辑,上下文类(设备)维护当前状态,当事件发生时,将事件转发给当前状态类处理,状态切换由状态类自行完成,彻底替代switch-case

代码示例(设备状态机)
cpp 复制代码
#include <iostream>
#include <string>

// 前向声明上下文类
class DeviceContext;

// 抽象状态:设备状态接口(所有状态实现此接口)
class DeviceState
{
public:
    virtual ~DeviceState() = default;
    virtual void handleEvent(DeviceContext *context, const std::string &event) = 0;
    virtual std::string getStateName() = 0;
};

// 上下文类:设备(维护当前状态,转发事件)
class DeviceContext
{
public:
    DeviceContext(DeviceState *init_state);
    // 切换状态(上下文仅提供状态切换接口)
    void setState(DeviceState *new_state);
    // 转发事件到当前状态
    void handleEvent(const std::string &event);
    ~DeviceContext();

private:
    DeviceState *current_state_;
};

// 声明所有具体状态类
class InitState;
class StandbyState;
class WorkState;
class FaultState;

// 具体状态1:初始化状态
class InitState : public DeviceState
{
public:
    void handleEvent(DeviceContext *context, const std::string &event) override;
    std::string getStateName() override;
};

// 具体状态2:待机状态
class StandbyState : public DeviceState
{
public:
    void handleEvent(DeviceContext *context, const std::string &event) override;
    std::string getStateName() override;
};

// 具体状态3:工作状态
class WorkState : public DeviceState
{
public:
    void handleEvent(DeviceContext *context, const std::string &event) override;
    std::string getStateName() override;
};

// 具体状态4:故障状态
class FaultState : public DeviceState
{
public:
    void handleEvent(DeviceContext *context, const std::string &event) override;
    std::string getStateName() override;
};

// -------------------------- 所有类声明完成后,统一实现成员函数 --------------------------
// 实现DeviceContext的成员函数
DeviceContext::DeviceContext(DeviceState *init_state)
    : current_state_(init_state)
{
    std::cout << "设备启动,初始状态:" << current_state_->getStateName() << std::endl;
}

void DeviceContext::setState(DeviceState *new_state)
{
    delete current_state_;
    current_state_ = new_state;
    std::cout << "设备状态切换:" << current_state_->getStateName() << std::endl;
}

void DeviceContext::handleEvent(const std::string &event)
{
    current_state_->handleEvent(this, event);
}

DeviceContext::~DeviceContext()
{
    delete current_state_;
}

// 实现InitState的成员函数
void InitState::handleEvent(DeviceContext *context, const std::string &event)
{
    if (event == "INIT_OK") {
        context->setState(new StandbyState()); 
    } else if (event == "INIT_FAIL") {
        context->setState(new FaultState());
    }
}

std::string InitState::getStateName()
{
    return "初始化状态";
}

// 实现StandbyState的成员函数
void StandbyState::handleEvent(DeviceContext *context, const std::string &event)
{
    if (event == "START_WORK") {
        context->setState(new WorkState()); 
    } else if (event == "POWER_OFF") {
        std::cout << "设备关机" << std::endl;
    }
}

std::string StandbyState::getStateName()
{
    return "待机状态";
}

// 实现WorkState的成员函数
void WorkState::handleEvent(DeviceContext *context, const std::string &event)
{
    if (event == "STOP_WORK") {
        context->setState(new StandbyState());
    } else if (event == "SENSOR_ERROR") {
        context->setState(new FaultState());
    }
}

std::string WorkState::getStateName()
{
    return "工作状态";
}

// 实现FaultState的成员函数
void FaultState::handleEvent(DeviceContext *context, const std::string &event)
{
    if (event == "RESET") {
        context->setState(new InitState());
    }
}

std::string FaultState::getStateName()
{
    return "故障状态";
}

// 测试函数
void test_state()
{
    std::cout << "\n===== 状态模式测试 =====" << std::endl;
    // 设备启动,初始状态为初始化
    DeviceContext device(new InitState());
    // 状态流转:初始化成功→待机→工作→传感器故障→复位→初始化
    device.handleEvent("INIT_OK");
    device.handleEvent("START_WORK");
    device.handleEvent("SENSOR_ERROR");
    device.handleEvent("RESET");
}
应用要点
  1. 状态单一职责:每个状态类仅处理该状态下的事件,避免逻辑臃肿;
  2. 状态切换可控:状态切换由状态类自行完成,上下文不参与具体的状态判断;
  3. 内存优化:嵌入式可将状态类设计为单例(饿汉式),避免频繁创建/销毁对象;
  4. 故障状态必做:设备的故障状态需单独封装,处理所有异常情况,保证系统鲁棒性。

4.3 策略模式

核心介绍

嵌入式开发中常遇到算法/协议需动态切换 的问题:根据信号强度自动切换WiFi/LoRa/4G通信、根据数据量动态选择压缩算法、根据功耗要求切换采集频率,传统用if-else实现的切换逻辑,扩展时需修改原有代码,违反开闭原则。

策略模式将每个算法/协议封装为一个独立的策略类,上下文类维护当前策略,运行时可动态切换策略,新增算法/协议时仅需新增策略类,无需修改原有代码,符合开闭原则。

代码示例(通信协议动态切换)
cpp 复制代码
#include <iostream>
#include <string>
#include <stdint.h>

// 抽象策略:通信策略接口(所有通信协议实现此接口)
class CommStrategy {
public:
    virtual ~CommStrategy() = default;
    virtual bool sendData(const std::string& data) = 0;
    virtual std::string getProtocolName() = 0;
};

// 具体策略1:WiFi通信(高速短距离)
class WiFiStrategy : public CommStrategy {
public:
    bool sendData(const std::string& data) override {
        std::cout << "WiFi(802.11n)发送:" << data << "(速率:100Mbps,距离:100m)" << std::endl;
        return true;
    }
    std::string getProtocolName() override { return "WiFi 802.11n"; }
};

// 具体策略2:LoRa通信(低速长距离)
class LoRaStrategy : public CommStrategy {
public:
    bool sendData(const std::string& data) override {
        std::cout << "LoRa(SX1278)发送:" << data << "(速率:1kbps,距离:5km)" << std::endl;
        return true;
    }
    std::string getProtocolName() override { return "LoRa SX1278"; }
};

// 上下文类:通信管理器(维护当前策略,动态切换)
class CommContext {
public:
    CommContext(CommStrategy* init_strategy) : current_strategy_(init_strategy) {
        std::cout << "通信管理器启动,当前协议:" << current_strategy_->getProtocolName() << std::endl;
    }
    // 动态切换策略(运行时切换,核心)
    void setStrategy(CommStrategy* new_strategy) {
        delete current_strategy_;
        current_strategy_ = new_strategy;
        std::cout << "通信协议切换:" << current_strategy_->getProtocolName() << std::endl;
    }
    // 执行通信(转发到当前策略)
    bool executeSend(const std::string& data) {
        return current_strategy_->sendData(data);
    }
    ~CommContext() {
        delete current_strategy_;
    }
private:
    CommStrategy* current_strategy_;
};

// 测试函数
void test_strategy() {
    std::cout << "\n===== 策略模式测试 =====" << std::endl;
    // 初始通信协议:WiFi
    CommContext comm_mgr(new WiFiStrategy());
    comm_mgr.executeSend("high_speed_data_123");

    // 模拟信号变弱,动态切换为LoRa(嵌入式真实场景:根据信号强度判断)
    comm_mgr.setStrategy(new LoRaStrategy());
    comm_mgr.executeSend("long_distance_data_456");
}
应用要点
  1. 策略轻量化:策略类的方法需极简,避免影响嵌入式实时性;
  2. 动态切换时机:根据硬件状态(如信号强度、功耗、数据量)在主线程中切换策略,避免在ISR中切换;
  3. 策略单例化:嵌入式可将策略类设计为单例(饿汉式),避免频繁创建/销毁对象;
  4. 异常处理:切换策略时需处理策略创建失败的情况,保证通信不中断。

5. 嵌入式Linux C++设计模式全维度对比表

模式类型 设计模式 核心思想 嵌入式典型应用 核心优点 核心缺点
创建型 单例(饿汉) 启动初始化,全局唯一实例 核心总线、系统日志、中断管理器 简单、无锁、ISR兼容、启动即可用 浪费内存、启动时间略长
创建型 单例(懒汉) 按需初始化,全局唯一实例 传感器、扩展模块、辅助工具 节省内存、按需初始化、可重初始化 实现复杂、不支持ISR、轻微锁开销
创建型 简单工厂 一个工厂创建所有产品 小规模系统、固定类型传感器 简单、开发快、内存占用极低 违反开闭原则、扩展/维护成本高
创建型 工厂方法 一个产品对应一个工厂 中大规模系统、可扩展外设 符合开闭原则、扩展灵活、易维护 类数量多、轻微内存增加
结构型 适配器模式 适配不兼容接口为标准接口 第三方驱动、旧版代码、芯片更换 解耦业务与底层、移植性强、修改少 增加类层级、轻微性能损耗
结构型 外观模式 为复杂子系统提供统一简单接口 网络协议栈、通信模块、文件系统 简化调用、屏蔽细节、统一资源管理 不符合单一职责、子系统修改需改外观
结构型 代理模式 为远程对象提供本地代理,屏蔽IPC 跨进程硬件访问、特权服务调用 解耦进程、权限隔离、屏蔽IPC细节 增加通信开销、轻微延迟升高
行为型 观察者模式 事件源通知观察者,实现事件驱动 按键中断、传感器报警、IO触发 解耦事件源与响应者、扩展灵活 观察者过多时通知耗时、需线程安全
行为型 状态模式 用类封装状态,替代switch-case 设备状态机、传感器工作流程 状态清晰、扩展灵活、易维护 状态类增多、轻微内存增加
行为型 策略模式 封装算法/协议,运行时动态切换 通信协议、压缩算法、采集频率 切换灵活、符合开闭原则、易扩展 策略类增多、需理解所有策略

6. 嵌入式Linux C++设计模式常见问题

单例模式专项

  1. :嵌入式Linux中,饿汉式和懒汉式单例该如何选择?
    :核心按资源类型和ISR需求选择:① 核心硬件(如核心总线、中断管理器)用饿汉式,兼顾ISR兼容性和线程安全;② 非核心硬件(如传感器、扩展模块)用懒汉式,节省嵌入式宝贵内存;③ 若系统内存极度受限(<128MB),优先懒汉式;④ 若需在中断中调用,必须用饿汉式。
  2. :懒汉式单例的双重检查锁在嵌入式C++中需要注意什么?
    :① 必须使用C++11及以上标准,保证内存模型的原子性,避免多线程下的半初始化问题;② 用std::mutex实现锁,避免嵌入式裸锁的死锁风险;③ 禁止在ISR中调用,锁会导致中断阻塞、系统死锁;④ 必须实现release()方法,手动释放内存和硬件资源。
  3. :嵌入式单例为什么必须实现release()方法?
    :因为嵌入式Linux无垃圾回收(GC),单例的资源(文件句柄、寄存器、内存)无法自动释放,若不手动释放,会导致资源泄漏,长期运行会让系统崩溃。

工厂模式专项

  1. :嵌入式系统中,简单工厂模式违反开闭原则,为什么还推荐使用?
    :① 嵌入式多为"小而美"系统,外设类型固定,无需频繁扩展,开闭原则的优先级低于简单、低内存;② 简单工厂类数量少,节省嵌入式宝贵的静态内存;③ 实现简单,开发和维护成本低,适合快速迭代;④ 后续若需扩展,可平滑迁移到工厂方法模式,接口完全兼容。
  2. :如何在嵌入式Linux中实现工厂方法模式的动态扩展?
    :① 定义抽象工厂和抽象产品接口,保证接口归一化;② 每个外设对应一个具体工厂和产品类;③ 将外设类型写入配置文件(如/etc/periph.conf),程序启动时动态读取;④ 用std::map建立"类型字符串→工厂类"的映射,动态创建工厂实例;⑤ 用智能指针管理实例,避免内存泄漏。
  3. :简单工厂和工厂方法的平滑迁移方案是什么?
    :先通过简单工厂实现核心功能,将产品的创建逻辑封装在工厂的create方法中;当需要扩展时,基于原有产品接口创建抽象工厂接口,将简单工厂的create方法拆分为多个具体工厂的create方法,业务层仅需将工厂的调用方式从"直接调用静态方法"改为"通过抽象工厂调用",原有产品代码无需修改。

通用设计模式专项

  1. :资源受限的嵌入式系统,是否适合大量使用设计模式?
    :不适合滥用,核心原则是够用即可、轻量级实现 :① 优先选择轻量级模式(如简单工厂>工厂方法,对象适配器>类适配器);② 避免使用复杂模式(如装饰器、迭代器、抽象工厂);③ 单个模式的类数量控制在合理范围,避免内存占用过高;④ 模式仅为工具,最终目标是代码可维护性与资源占用的平衡
  2. :嵌入式设计模式与桌面/服务器端设计模式的核心区别是什么?
    :核心区别是嵌入式需兼顾硬件特性和资源限制:① 嵌入式关注ISR兼容性、线程安全(无锁/轻锁),桌面端无需考虑;② 嵌入式优先内存占用,选择轻量级实现,桌面端可接受类数量增多;③ 嵌入式模式需贴合硬件(如单例对应硬件唯一,状态模式对应设备状态机),桌面端更关注业务逻辑;④ 嵌入式模式需手动释放资源,桌面端有GC自动回收。
  3. :嵌入式开发中,设计模式的核心落地原则是什么?
    :三大核心原则:① 硬件优先 :所有模式的实现都需贴合嵌入式硬件特性(如唯一、中断、低功耗);② 资源可控 :优先轻量级实现,避免内存/算力的过度消耗;③ 最小修改:通过模式解耦硬件与业务,让底层硬件的修改对业务层的影响最小。

7. 总结

  1. 核心模式:单例(饿汉+懒汉)、工厂(简单+工厂方法)、适配器、观察者、状态模式;
  2. 按场景选择模式:根据嵌入式系统的规模、外设扩展需求、硬件类型选择合适的模式;
  3. 兼顾硬件与资源:所有模式的实现都需考虑ISR兼容性、线程安全、内存占用,避免脱离嵌入式实际;
  4. 拒绝过度设计:模式是为了解决问题,而非为了使用模式,小规模系统用简单代码即可解决的问题,无需强行使用设计模式。

核心宗旨 :嵌入式设计模式的核心是让硬件迭代更轻松、代码维护更简单、系统运行更稳定 ,在资源受限的嵌入式场景下,找到设计优雅与性能高效的平衡点,才是最终的目标。

相关推荐
挖矿大亨2 小时前
C++中空指针访问成员函数
开发语言·c++
txinyu的博客2 小时前
解析muduo源码之 Socket.h & Socket.cc
c++
吕司2 小时前
Linux系统安装MySQL
linux·运维·服务器
阿猿收手吧!2 小时前
【C++】模板偏特化与std::move深度解析
服务器·c++
问好眼2 小时前
【信息学奥赛一本通】1296:开餐馆
c++·算法·动态规划·信息学奥赛
Qt学视觉2 小时前
3D3-PCL全面总结
c++·opencv·3d
短剑重铸之日3 小时前
《设计模式》第七篇:适配器模式
java·后端·设计模式·适配器模式
愚者游世3 小时前
力扣解决二进制&题型常用知识点梳理
c++·程序人生·算法·leetcode·职场和发展·改行学it
serve the people3 小时前
python环境搭建 (九) 极简日志工具 loguru
linux·服务器·python