策略模式
迭代器模式
适配器模式
工厂模式
超级工厂模式
享元模式
代理模式
模板方法模式
0 设计模式的SOLID原则
SOLID是一个缩写词,代表以下设计原则(及其缩写):
Single Responsibility Principle (SRP)
Open-Closed Principle (OCP)
Liskov Substitution Principle (LSP)
Interface Segregation Principle (ISP)
Dependency Inversion Principle (DIP)
这五个特定的主题贯穿了对模式和软件设计的一般讨论,所以在我们深入研究设计模式之前(我知道你们都很渴望),我们将简要回顾一下SOLID原则是什么。
0.1 Single Responsibility Principle (SRP): 单一职责原则
一个类或者一个模块只做一件事。让一个类或者一个模块专注于单一的功能,减少功能之间的耦合程度。这样做在需要修改某个功能时,就不会影响到其他的功能。
0.2 Open Closed Principle(OCP):开闭原则
对扩展开放,对修改关闭。一个类独立之后就不应该去修改它,而是以扩展的方式适应新需求。
0.3 Liskov Substitution Principle(LSP):里氏替换原则
所有基类出现的地方都可以用派生类替换而不会让程序产生错误,派生类可以扩展基类的功能,但不能改变基类原有的功能。
0.4 Interface Segregation Principle(ISP):接口隔离原则
一个接口应该拥有尽可能少的行为,使其精简单一。对于不同的功能的模块分别使用不同接口,而不是使用同一个通用的接口。
0.5 Dependence Inversion Principle(DIP):依赖倒置原则
高级模块不应该依赖低级模块,而是依赖抽象接口,通过抽象接口使用对应的低级模块。
1 策略模式
策略模式(Strategy Pattern)是一种行为型设计模式,其核心思想是将算法家族封装起来,让它们之间可以互相替换,从而使算法的变化独立于使用算法的客户端。这种模式特别适合处理 "同一问题存在多种解决方案,且需要动态选择其中一种" 的场景。
策略模式包含三个关键角色:
- 环境类(Context):持有策略对象的引用,负责调用具体策略
- 抽象策略接口(Strategy):定义所有具体策略的公共接口
- 具体策略类(ConcreteStrategy):实现抽象策略接口,包含具体算法
适用场景
- 一个问题有多种解决方案,需要动态选择其中一种
- 算法需要被复用或替换
- 避免使用多重条件判断语句
代码示例:
#include <iostream>
#include <string>
#include <memory>
#include <chrono>
#include <cstdint>
// 信号数据结构
struct SignalData {
uint32_t id; // 信号ID
uint64_t timestamp; // 时间戳
std::string raw_data; // 原始数据
bool current_state; // 当前状态
bool previous_state; // 上一次状态
};
// 故障信息结构
struct FaultInfo {
uint32_t signal_id;
std::string fault_type;
uint64_t timestamp;
};
// --------------------------
// 1. 抽象策略接口:信号处理策略
// --------------------------
class SignalProcessingStrategy {
public:
virtual ~SignalProcessingStrategy() = default;
// 判断信号状态是否改变
virtual bool isStateChanged(const SignalData& signal) = 0;
// 检查信号是否有故障
virtual bool hasFault(const SignalData& signal) = 0;
// 获取故障信息
virtual FaultInfo getFaultInfo(const SignalData& signal) = 0;
// 获取策略名称
virtual std::string getStrategyName() const = 0;
};
// --------------------------
// 2. 具体策略实现
// --------------------------
// 高频信号处理策略
class HighFrequencyStrategy : public SignalProcessingStrategy {
public:
bool isStateChanged(const SignalData& signal) override {
// 高频信号状态变化判断:阈值更敏感
return signal.current_state != signal.previous_state;
}
bool hasFault(const SignalData& signal) override {
// 高频信号故障判断:检查是否连续3次状态异常
static std::unordered_map<uint32_t, int> fault_counter;
if (signal.current_state == false) { // 假设false表示异常
fault_counter[signal.id]++;
return fault_counter[signal.id] >= 3;
} else {
fault_counter[signal.id] = 0;
return false;
}
}
FaultInfo getFaultInfo(const SignalData& signal) override {
return {
signal.id,
"高频信号连续异常",
signal.timestamp
};
}
std::string getStrategyName() const override {
return "高频信号处理策略";
}
};
// 低频信号处理策略
class LowFrequencyStrategy : public SignalProcessingStrategy {
public:
bool isStateChanged(const SignalData& signal) override {
// 低频信号状态变化判断:需连续2次确认
static std::unordered_map<uint32_t, bool> last_check;
bool result = (last_check[signal.id] == signal.current_state) &&
(signal.current_state != signal.previous_state);
last_check[signal.id] = signal.current_state;
return result;
}
bool hasFault(const SignalData& signal) override {
// 低频信号故障判断:检查超时(500ms未更新)
auto now = std::chrono::duration_cast<std::chrono::milliseconds>
(std::chrono::system_clock::now().time_since_epoch()).count();
return (now - signal.timestamp) > 500;
}
FaultInfo getFaultInfo(const SignalData& signal) override {
return {
signal.id,
"低频信号超时未更新",
signal.timestamp
};
}
std::string getStrategyName() const override {
return "低频信号处理策略";
}
};
// E2E信号处理策略
class E2EStrategy : public SignalProcessingStrategy {
public:
bool isStateChanged(const SignalData& signal) override {
// E2E信号状态变化判断:检查校验和
uint8_t checksum = calculateChecksum(signal.raw_data);
return checksum != signal.raw_data.back(); // 假设最后一个字节是校验和
}
bool hasFault(const SignalData& signal) override {
// E2E信号故障判断:校验和错误或计数器异常
return isStateChanged(signal) || checkCounter(signal);
}
FaultInfo getFaultInfo(const SignalData& signal) override {
if (isStateChanged(signal)) {
return {signal.id, "E2E信号校验和错误", signal.timestamp};
} else {
return {signal.id, "E2E信号计数器异常", signal.timestamp};
}
}
std::string getStrategyName() const override {
return "E2E信号处理策略";
}
private:
// 计算校验和(简单实现)
uint8_t calculateChecksum(const std::string& data) {
uint8_t sum = 0;
for (size_t i = 0; i < data.size() - 1; ++i) { // 排除最后一个字节
sum += static_cast<uint8_t>(data[i]);
}
return sum;
}
// 检查计数器(简单实现)
bool checkCounter(const SignalData& signal) {
static std::unordered_map<uint32_t, uint8_t> last_counter;
uint8_t current_counter = static_cast<uint8_t>(signal.raw_data[0]);
bool result = (current_counter - last_counter[signal.id]) % 256 != 1;
last_counter[signal.id] = current_counter;
return result;
}
};
// --------------------------
// 3. 环境类:信号处理器
// --------------------------
class SignalProcessor {
private:
std::unique_ptr<SignalProcessingStrategy> strategy_;
// 上传故障信息到系统
void uploadFault(const FaultInfo& fault) {
std::cout << "\n=== 上传故障信息 ===" << std::endl;
std::cout << "信号ID: " << fault.signal_id << std::endl;
std::cout << "故障类型: " << fault.fault_type << std::endl;
std::cout << "时间戳: " << fault.timestamp << std::endl;
std::cout << "===================\n" << std::endl;
}
public:
// 构造函数:指定初始策略
explicit SignalProcessor(std::unique_ptr<SignalProcessingStrategy> strategy)
: strategy_(std::move(strategy)) {}
// 切换策略
void setStrategy(std::unique_ptr<SignalProcessingStrategy> strategy) {
if (strategy) {
std::cout << "\n切换信号处理策略: " << strategy_->getStrategyName()
<< " -> " << strategy->getStrategyName() << std::endl;
strategy_ = std::move(strategy);
}
}
// 处理信号的主流程
void processSignal(SignalData& signal) {
std::cout << "\n----- 处理信号 (ID: " << signal.id << ") -----" << std::endl;
std::cout << "使用策略: " << strategy_->getStrategyName() << std::endl;
// 检查状态是否改变
if (strategy_->isStateChanged(signal)) {
std::cout << "信号状态发生改变" << std::endl;
// 更新上一次状态
signal.previous_state = signal.current_state;
} else {
std::cout << "信号状态未改变" << std::endl;
}
// 检查是否有故障
if (strategy_->hasFault(signal)) {
std::cout << "检测到信号故障!" << std::endl;
// 上传故障信息
FaultInfo fault = strategy_->getFaultInfo(signal);
uploadFault(fault);
} else {
std::cout << "信号正常" << std::endl;
}
}
};
// --------------------------
// 4. 客户端代码
// --------------------------
int main() {
// 创建信号处理器,初始使用高频信号策略
auto processor = std::make_unique<SignalProcessor>(
std::make_unique<HighFrequencyStrategy>()
);
// 处理高频信号
SignalData high_freq_signal{
0x101, // ID
1620000000001, // 时间戳
"", // 原始数据
true, // 当前状态
false // 上一次状态
};
processor->processSignal(high_freq_signal);
// 再次处理高频信号(模拟连续异常)
high_freq_signal.current_state = false;
high_freq_signal.timestamp = 1620000000002;
processor->processSignal(high_freq_signal);
high_freq_signal.timestamp = 1620000000003;
processor->processSignal(high_freq_signal);
high_freq_signal.timestamp = 1620000000004;
processor->processSignal(high_freq_signal); // 第三次异常,触发故障
// 切换到低频信号策略
processor->setStrategy(std::make_unique<LowFrequencyStrategy>());
// 处理低频信号
SignalData low_freq_signal{
0x201, // ID
1620000000005, // 时间戳
"", // 原始数据
true, // 当前状态
true // 上一次状态
};
processor->processSignal(low_freq_signal);
// 处理超时的低频信号
low_freq_signal.timestamp = 1620000000000; // 过期的时间戳
processor->processSignal(low_freq_signal); // 触发超时故障
// 切换到E2E信号策略
processor->setStrategy(std::make_unique<E2EStrategy>());
// 处理E2E信号(校验和正确)
SignalData e2e_signal{
0x301, // ID
1620000000006, // 时间戳
"E2E_OK\x05", // 原始数据,最后一个字节是校验和
true, // 当前状态
true // 上一次状态
};
processor->processSignal(e2e_signal);
// 处理有问题的E2E信号(校验和错误)
e2e_signal.raw_data = "E2E_BAD\x00"; // 错误的校验和
e2e_signal.timestamp = 1620000000007;
processor->processSignal(e2e_signal); // 触发校验和故障
return 0;
}
代码解析,
抽象策略接口(SignalProcessingStrategy)
定义了所有信号处理策略的统一接口,包括状态变化判断、故障检测、故障信息获取等方法
所有具体信号类型的处理策略都必须实现这些接口
具体策略实现
高频信号策略(HighFrequencyStrategy):对状态变化敏感,连续 3 次异常即判定为故障
低频信号策略(LowFrequencyStrategy):状态变化需要连续确认,超时未更新判定为故障
E2E 信号策略(E2EStrategy):基于校验和和计数器判断状态和故障
环境类(SignalProcessor)
持有一个策略接口的引用,封装了信号处理的主流程
提供切换策略的方法,使得可以动态改变信号处理方式
负责调用策略的方法进行状态判断和故障处理,并统一处理故障上传
2 适配器模式
适配器模式(Adapter Pattern)是一种结构型设计模式,其核心是将一个类的接口转换成客户端期望的另一种接口,使原本因接口不兼容而无法协作的类能够一起工作。在智能驾驶开发中,由于传感器、算法、硬件的多样性,适配器模式被广泛用于解决接口适配问题。
核心解决的问题
- 接口兼容性:当新组件(如第三方算法、新型传感器)的接口与现有系统不一致时,无需修改原有代码即可使其兼容。
- 复用性扩展:复用已有功能组件(如开源算法),通过适配使其融入现有架构,避免重复开发。
- 系统稳定性:隔离外部组件的接口变化,原有系统代码无需修改,降低维护成本。
核心角色
- 目标接口(Target):客户端期望的统一接口,定义了系统需要的功能规范。
- 适配者(Adaptee):需要被适配的现有类或接口(如第三方算法、硬件驱动),其接口与目标接口不兼容。
- 适配器(Adapter):实现目标接口,内部持有适配者的引用,将目标接口的调用转换为对适配者的调用。
两种实现方式
- 类适配器:通过多重继承(继承目标接口和适配者)实现适配(C++ 支持,但因多重继承复杂性较少使用)。
- 对象适配器:通过组合(持有适配者对象)实现适配,更灵活,是主流实现方式。
代码示例:
#include <iostream>
#include <vector>
#include <string>
#include <memory>
// ------------------------------
// 1. 目标接口(Target):系统期望的统一检测接口
// ------------------------------
struct DetectionResult {
std::string class_name; // 目标类别(如"car"、"pedestrian")
float x; // 检测框x坐标
float y; // 检测框y坐标
float width; // 检测框宽度
float height; // 检测框高度
float confidence; // 置信度
};
// 目标接口:统一的目标检测器接口
class TargetDetector {
public:
virtual ~TargetDetector() = default;
// 初始化检测器(返回是否成功)
virtual bool init(const std::string& model_path) = 0;
// 检测接口(输入图像数据,输出检测结果)
virtual std::vector<DetectionResult> detect(
const unsigned char* image_data,
int width,
int height) = 0;
// 获取算法名称
virtual std::string getName() const = 0;
};
// ------------------------------
// 2. 适配者(Adaptee):需要被适配的第三方算法
// ------------------------------
// 适配者1:YOLO算法(接口与目标接口不兼容)
class YoloAlgorithm {
public:
// YOLO的初始化接口(参数不同)
bool loadModel(const std::string& model_path, int gpu_id) {
std::cout << "YoloAlgorithm: 加载模型 " << model_path
<< " (GPU ID: " << gpu_id << ")\n";
return true; // 模拟成功加载
}
// YOLO的检测接口(输入输出格式不同)
struct YoloResult {
int class_id; // 类别ID(而非名称)
float bbox[4]; // [x1, y1, x2, y2](与目标接口的宽高格式不同)
float score; // 分数(对应置信度)
};
std::vector<YoloResult> runDetection(
const unsigned char* img,
int w,
int h,
float threshold) { // 多了阈值参数
// 模拟检测结果
return {
{0, {100, 200, 300, 400}, 0.92}, // 假设class_id=0对应"car"
{1, {50, 150, 150, 350}, 0.88} // 假设class_id=1对应"pedestrian"
};
}
};
// 适配者2:Faster R-CNN算法(接口不同)
class FasterRCNNAlgorithm {
public:
// Faster R-CNN的初始化接口(无GPU参数)
void initialize(const std::string& model_file, const std::string& config_file) {
std::cout << "FasterRCNNAlgorithm: 初始化模型 " << model_file
<< " (配置: " << config_file << ")\n";
}
// Faster R-CNN的检测接口(返回格式不同)
std::vector<std::tuple<std::string, float, float, float, float, float>> detectObjects(
const unsigned char* image,
int width,
int height) {
// 模拟检测结果(格式:(类别名, x, y, w, h, 置信度))
return {
{"car", 120, 220, 180, 190, 0.90},
{"cyclist", 80, 180, 60, 120, 0.85}
};
}
};
// ------------------------------
// 3. 适配器(Adapter):将适配者转换为目标接口
// ------------------------------
// 适配器1:YOLO算法适配器
class YoloAdapter : public TargetDetector {
private:
YoloAlgorithm yolo_; // 持有适配者对象
int gpu_id_; // YOLO特有的参数
float threshold_; // YOLO特有的参数
public:
YoloAdapter(int gpu_id = 0, float threshold = 0.5f)
: gpu_id_(gpu_id), threshold_(threshold) {}
bool init(const std::string& model_path) override {
// 适配初始化接口:将目标接口的参数转换为YOLO需要的参数
return yolo_.loadModel(model_path, gpu_id_);
}
std::vector<DetectionResult> detect(
const unsigned char* image_data,
int width,
int height) override {
// 1. 调用YOLO的检测接口
auto yolo_results = yolo_.runDetection(
image_data, width, height, threshold_);
// 2. 转换结果格式(YOLO格式 → 目标接口格式)
std::vector<DetectionResult> results;
for (const auto& yolo_res : yolo_results) {
DetectionResult res;
// 转换类别ID为类别名
res.class_name = (yolo_res.class_id == 0) ? "car" :
(yolo_res.class_id == 1) ? "pedestrian" : "unknown";
// 转换边界框格式(x1,y1,x2,y2 → x,y,width,height)
res.x = yolo_res.bbox[0];
res.y = yolo_res.bbox[1];
res.width = yolo_res.bbox[2] - yolo_res.bbox[0];
res.height = yolo_res.bbox[3] - yolo_res.bbox[1];
res.confidence = yolo_res.score;
results.push_back(res);
}
return results;
}
std::string getName() const override {
return "YOLO Detector (Adapted)";
}
};
// 适配器2:Faster R-CNN算法适配器
class FasterRCNNAdapter : public TargetDetector {
private:
FasterRCNNAlgorithm frcnn_; // 持有适配者对象
std::string config_file_; // Faster R-CNN特有的配置文件
public:
FasterRCNNAdapter(std::string config_file)
: config_file_(std::move(config_file)) {}
bool init(const std::string& model_path) override {
// 适配初始化接口:Faster R-CNN需要模型文件和配置文件
frcnn_.initialize(model_path, config_file_);
return true; // FasterRCNNAlgorithm的initialize无返回值,默认成功
}
std::vector<DetectionResult> detect(
const unsigned char* image_data,
int width,
int height) override {
// 1. 调用Faster R-CNN的检测接口
auto frcnn_results = frcnn_.detectObjects(image_data, width, height);
// 2. 转换结果格式(Faster R-CNN格式 → 目标接口格式)
std::vector<DetectionResult> results;
for (const auto& frcnn_res : frcnn_results) {
DetectionResult res;
res.class_name = std::get<0>(frcnn_res);
res.x = std::get<1>(frcnn_res);
res.y = std::get<2>(frcnn_res);
res.width = std::get<3>(frcnn_res);
res.height = std::get<4>(frcnn_res);
res.confidence = std::get<5>(frcnn_res);
results.push_back(res);
}
return results;
}
std::string getName() const override {
return "Faster R-CNN Detector (Adapted)";
}
};
// ------------------------------
// 客户端代码:智能驾驶感知系统
// ------------------------------
void runPerceptionSystem(TargetDetector& detector, const std::string& model_path) {
// 初始化检测器(统一接口)
if (!detector.init(model_path)) {
std::cerr << "检测器初始化失败!\n";
return;
}
std::cout << "\n运行检测器: " << detector.getName() << "\n";
// 模拟输入图像数据(实际中是摄像头/激光雷达数据)
unsigned char dummy_image[100 * 100] = {0}; // 占位图像数据
// 执行检测(统一接口)
auto results = detector.detect(dummy_image, 100, 100);
// 输出检测结果
std::cout << "检测到 " << results.size() << " 个目标:\n";
for (const auto& res : results) {
std::cout << "- " << res.class_name
<< " (置信度: " << res.confidence << "): "
<< "位置(" << res.x << ", " << res.y << "), "
<< "大小(" << res.width << "x" << res.height << ")\n";
}
}
int main() {
// 1. 使用YOLO适配器
YoloAdapter yolo_adapter(0, 0.6f); // 指定GPU ID和阈值
runPerceptionSystem(yolo_adapter, "yolo_model.pt");
// 2. 使用Faster R-CNN适配器
FasterRCNNAdapter frcnn_adapter("frcnn_config.yaml"); // 指定配置文件
runPerceptionSystem(frcnn_adapter, "frcnn_model.pth");
return 0;
}
代码解析
在智能驾驶感知系统的示例中,适配器模式解决了 "多算法接口不统一" 的核心问题:
1 目标接口(TargetDetector):
定义了感知系统需要的统一接口(init初始化、detect检测),所有检测器必须遵循此规范,确保系统上层代码(如决策模块)能统一调用。
2 适配者(YoloAlgorithm、FasterRCNNAlgorithm):
代表第三方算法,它们的接口各不相同:
YOLO 需要loadModel(带 GPU ID)和runDetection(带阈值);
Faster R-CNN 需要initialize(带配置文件)和detectObjects(返回格式不同)。
3 适配器(YoloAdapter、FasterRCNNAdapter):
实现TargetDetector接口,内部持有适配者对象,完成三项核心工作:
接口转换:将init调用转换为适配者的初始化方法(如loadModel);
参数适配:补充适配者需要的额外参数(如 YOLO 的 GPU ID、Faster R-CNN 的配置文件);
数据转换:将适配者返回的检测结果(如 YOLO 的bbox[x1,y1,x2,y2])转换为系统统一的DetectionResult格式。
4 客户端(runPerceptionSystem):
仅依赖TargetDetector接口,无需关心具体算法的实现细节,实现了 "算法替换不影响上层代码" 的灵活性(如切换 YOLO 和 Faster R-CNN 时,客户端代码无需修改)。
3 迭代器模式
4 享元模式
5 代理模式
6 模板方法模式
6.1 模板方法模式适用场景
- 父类视角 : 一次性 实现 一个算法 不变的部分 , 并将 可变部分 留给 子类 实现 ;
- 子类视角 : 各个子类中 , 公共部分 被提取出来 , 集中到一个公共的父类中 , 避免代码重复 ;
模板方法模式的目的是 让 子类可以扩展 或 具体实现固定方法的某个具体的步骤 ; 对于模板来说 , 是一套固定的算法 , 通过子类 可以扩展 固定算法中某些算法步骤 ;
6.2 模板方法模式优缺点
模板方法模式 将 不变的行为定义在父类中 , 去除子类的重复代码 , 体现其优势 , 提供了一个很好的代码复用平台 ;
模板方法模式缺点 :
- 增加复杂性 : 类 数量增加 , 增加了系统复杂性 ; 引入了抽象类 , 对于每个实现 , 都需要定义一个子类 ;
- 继承缺点 : 模板方法 主要 通过 继承实现 , 继承关系自身就有缺点 , 如果父类增加新的抽象方法 , 所有的子类都要修改一遍 ;
6.3 代码示例
#include <iostream>
#include <memory>
#include <string>
// 抽象基类 - 定义算法骨架
class InferencePipeline {
public:
virtual ~InferencePipeline() = default;
// 模板方法 - 定义不变的流程
void execute() {
preProcess(); // 固定前处理
inference(); // 平台相关的推理
postProcess(); // 固定后处理
}
protected:
// 固定实现的前后处理
void preProcess() {
std::cout << "Common pre-processing: Data loading, normalization, etc." << std::endl;
}
void postProcess() {
std::cout << "Common post-processing: Result formatting, output generation, etc." << std::endl;
}
// 平台特定的推理 - 由子类实现
virtual void inference() = 0;
};
// Thor平台实现
class ThorInferencePipeline : public InferencePipeline {
protected:
void inference() override {
std::cout << "Thor platform inference: Optimized for Thor architecture" << std::endl;
// Thor特定的推理代码
}
};
// J6M平台实现
class J6MInferencePipeline : public InferencePipeline {
protected:
void inference() override {
std::cout << "J6M platform inference: Optimized for J6M architecture" << std::endl;
// J6M特定的推理代码
}
};
// 使用示例
int main() {
std::cout << "=== Thor Platform ===" << std::endl;
auto thorPipeline = std::make_unique<ThorInferencePipeline>();
thorPipeline->execute();
std::cout << "\n=== J6M Platform ===" << std::endl;
auto j6mPipeline = std::make_unique<J6MInferencePipeline>();
j6mPipeline->execute();
return 0;
}
代码解析:
推理流程分为,前处理,推理,后处理,不同的平台,比喻thor和j6m,只有推理的代码不一样,前后处理的代码一样