C++设计模式--策略模式与观察者模式
目录
观察者模式
策略模式
1.算法封装和互相替换
2.运行时动态切换
3.在 Callback 类中的体现
先看这样一个头文件callback.h
#ifndef CALLBACK_H
#define CALLBACK_H// class PalmObject;
// class FaceObject;
// class PoseObject;
class Callback
{
public:Callback();virtual ~Callback();// 发送关键点检测结果(如人脸、手势的关键点坐标)virtual void sendLandmarkToLocal(long frameID, const char* data, int len) = 0;// 发送目标人脸检测/识别结果virtual void sendTargetFace(const char * result, int len) = 0; // 错误回调,当处理过程中发生错误时调用virtual void onError(int error) = 0;// void sendDebugHandsData(PalmObject* obj, int w, int h, bool isLeft);// void sendDebugFaceData(FaceObject* obj, int w, int h);// void sendDebugPoseData(PoseObject* obj, int w, int h);// 通知处理超时void sendMsgForHandleTooLong(uint64_t dura_frame, uint64_t dura_pose, uint64_t dura_face,uint64_t dura_hand, uint64_t dura_eulur);// 发送性能统计信息(帧率、处理时间等)void sendMsgForStatics(uint64_t avm_handle_frame_time, uint64_t avm_handle_pose_time,uint64_t avm_handle_face_time, uint64_t avm_handle_hand_time,uint64_t avm_handle_eulur_time, int input_frame_num,int detect_pose_fps, int detect_face_fps,int detect_hand_fps, int detect_hand_left_fps,int detect_hand_right_fps, int detect_hand_two_fps,int eulur_holistic_fps);// 发送人脸特征数据void sendTargetFaceFeature(const char * result, int len);// 发送完整的推理结果(关键点+特征)void sendInferceHolisticData(long frameID, long frame_duration, long ai_duration,const char* holistic_landmark_data, int len,const char* featureData, int featureDataLen);// 发送后处理数据void sendPostProcessData(uint64_t frameID, int dura,const char* holistic_data, int holistic_len);private:uint64_t m_uuid;
};
#endif
我们可以发现这个C++类是一个抽象基类,主要用于定义回调接口,实现异步通信和事件通知机制,它在典型的数据处理流水线(如计算机视觉、AI推理应用)中扮演着关键角色。
这个类是一个典型的例子,主要作用是为底层算法、处理模块提供一个统一的接口,使其能够向上层应用(如UI、业务逻辑层)发送处理结果、错误信息和性能数据,而无需关心上层如何具体实现这些功能。
同时在这个类中体现了两种重要的设计模式:策略模式和观察者模式,借助这个类来理解下这两个比较常见的设计模式。
观察者模式
核心思想:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
以Callback类为例:
主题(被观察者)- 处理引擎
class ProcessingEngine {
private:Callback* observer; // 观察者public:void setObserver(Callback* cb) {this->observer = cb;}void processFrame(const Frame& frame) {try {// 处理帧数据...auto landmarks = detectLandmarks(frame);// 通知观察者(回调)if (observer) {observer->sendLandmarkToLocal(frame.id, landmarks.data(), landmarks.size());}} catch (const exception& e) {// 发生错误时通知观察者if (observer) {observer->onError(ERROR_CODE);}}}
};
观察者实现
class MyApp : public Callback {
private:ProcessingEngine engine;public:MyApp() {engine.setObserver(this); // 注册为观察者}// 当被观察者状态变化时被调用void sendLandmarkToLocal(long frameID, const char* data, int len) override {updateUIWithLandmarks(data, len);}void onError(int error) override {showErrorMessage(error);}
};
这种设计模式的好处在于,处理引擎不需要知道谁在接收结果,当状态变化立即通知所有观察者,并且可以动态添加或者移除观察者。
策略模式
核心思想:定义一系列算法,将每个算法封装起来,并且使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户端。
先举一个好理解的例子:
1.算法封装和互相替换
想象有一个导航应用,它提供不同的路线规划策略:最快路线算法,最短路线算法,避开收费算法。
这些不同的算法就是不同的"策略",它们可以在运行时根据用户选择动态切换,而不需要修改导航应用的核心代码。
代码示例:
1.定义策略接口:
// 策略接口 - 定义算法的共同契约
class RouteStrategy {
public:virtual ~RouteStrategy() {}virtual void calculateRoute(const Point& start, const Point& end) = 0;
};
2.实现具体的策略算法
// 具体策略1: 最快路线算法
class FastestRoute : public RouteStrategy {
public:void calculateRoute(const Point& start, const Point& end) override {cout << "Calculating fastest route from " << start << " to " << end << endl;// 实现最快路线的具体算法}
};// 具体策略2: 最短路线算法
class ShortestRoute : public RouteStrategy {
public:void calculateRoute(const Point& start, const Point& end) override {cout << "Calculating shortest route from " << start << " to " << end << endl;// 实现最短距离的具体算法}
};// 具体策略3: 避开收费算法
class AvoidTollsRoute : public RouteStrategy {
public:void calculateRoute(const Point& start, const Point& end) override {cout << "Calculating toll-free route from " << start << " to " << end << endl;// 实现避开收费站的具体算法}
};
3.上下文类--使用策略
class NavigationApp {
private:RouteStrategy* strategy; // 持有策略对象的指针public:// 设置策略 - 这就是动态切换的关键!void setStrategy(RouteStrategy* newStrategy) {strategy = newStrategy;}void navigate(const Point& start, const Point& end) {if (strategy) {strategy->calculateRoute(start, end); // 委托给当前策略执行}}
};
2.运行时动态切换
int main() {NavigationApp app;Point start("北京"), end("上海");// 场景1: 用户想要最快路线FastestRoute fastest;app.setStrategy(&fastest); // 动态设置为最快路线策略app.navigate(start, end); // 输出: Calculating fastest route...// 场景2: 用户改变主意,想要避开收费AvoidTollsRoute noTolls; app.setStrategy(&noTolls); // 动态切换为避开收费策略app.navigate(start, end); // 输出: Calculating toll-free route...// 场景3: 途中用户又想走最短距离ShortestRoute shortest;app.setStrategy(&shortest); // 再次动态切换app.navigate(start, end); // 输出: Calculating shortest route...return 0;
}
3.在 Callback 类中的体现
回到最初的Callback类,策略模式的应用:
// 策略接口定义
class Callback {
public:virtual void sendLandmarkToLocal(long frameID, const char* data, int len) = 0;virtual void sendTargetFace(const char* result, int len) = 0; virtual void onError(int error) = 0;// ... 其他方法
};// 不同的处理策略
class RealTimeDisplay : public Callback {void sendLandmarkToLocal(...) override {// 策略1: 实时UI显示updateDisplay(data, len);}
};class DataLogger : public Callback {void sendLandmarkToLocal(...) override {// 策略2: 数据记录和分析logForAnalysis(frameID, data, len);}
};class NetworkForwarder : public Callback {void sendLandmarkToLocal(...) override {// 策略3: 网络传输sendToCloud(data, len);}
};// 运行时动态切换
ProcessingEngine engine;
RealTimeDisplay displayStrategy;
DataLogger loggingStrategy;// 根据应用模式动态切换回调策略
if (isDebugMode) {engine.setCallback(&loggingStrategy);
} else {engine.setCallback(&displayStrategy);
}// 甚至可以在运行时根据用户操作切换
userButton.onClick([&]() {engine.setCallback(&networkStrategy); // 动态切换!
});
每个算法被封装在独立的类中,这些类实现相同的接口,因此可以互相替换,替换发生在运行时,也不需要重新编译代码,而且可以根据配置、用户输入、系统状态等动态选择算法,这种设计方式让系统变得极其灵活和可维护。
在这个Callback类中,两种模式完美结合:观察者模式确保当数据处理完成时及时通知上层,策略模式让上层可以自由决定如何处理接收到的数据。
两种设计模式的区别在于策略模式核心关注的是如何做(算法的选择与替换),观察者模式则关注的是何时做(状态变化的通知机制)。