☰
简历技术栈全面复习——软件架构与设计模式
2026/9/30 7:11:31 网站建设 项目流程

1、Abstract Class 和 Interface

C++里面没有 Java 那种严格意义上的interface关键字。

通常我们用:

class MotionInput

这种纯虚函数抽象类来实现接口。

例如:

class MotionInput { public: virtual ~MotionInput() = default; virtual bool start() = 0; virtual bool getFrame(MotionFrame&) = 0; virtual void stop() = 0; };

它基本就是接口。

而“抽象类”可以比接口更丰富:

class MotionInput { public: virtual bool start() = 0; void printStatus() { // 公共实现 } protected: int timeout_ms; };

所以简单记:

Interface:主要规定规范。

Abstract Class:既可以规定规范,也可以提供公共实现。

你现在做工程时,先把这个区别理解到这里就够了。


2、Strategy:策略模式

这是你项目里非常重要的一个。你的问题是:

不同动捕源,都是“获取人体运动数据”,但具体获取方式不同。

这天然适合 Strategy。

例如:

MotionInput │ ┌────────────┼────────────┐ ↓ ↓ ↓ Xsens策略 Axis策略 Noitom策略

上层:

MotionInput* input;

运行时可以:

配置文件: source_type = xsens

就使用:

XsensUdpSource

改成:

source_type = axis_bvh

就使用:

AxisBvhSource

上层 GMR 不需要修改。


Strategy 到底解决什么问题?一句话:

把“可替换的算法/行为”独立出来。

例如你的系统:

人体数据输入 ↓ GMR ↓ 机器人关节

输入策略可以替换:

MotionInput │ ┌──────────┼──────────┐ ↓ ↓ ↓ Xsens Axis Noitom

GMR 永远不变。

所以:

换数据源 ↓ 换 Strategy ↓ 不用改 GMR

这就是 Strategy。


3、Adapter:适配器模式

这个和 Strategy 很容易混。

假设 Noitom SDK 给你的数据是:

NoitomPose

Axis 给你:

AxisPose

Xsens:

XsensPose

但是你的系统统一需要:

MotionFrame

怎么办?

使用 Adapter。

Noitom SDK ↓ NoitomAdapter ↓ MotionFrame

例如:

class NoitomAdapter : public MotionInput { public: bool getFrame(MotionFrame& frame) override { NoitomPose pose = sdk.read(); // 转换成统一格式 frame = convert(pose); return true; } };

所以:

Adapter解决“接口不兼容”。

Strategy

解决:

“我有多个可替换的实现。”

例如:

Xsens Axis Noitom

都是不同的数据输入策略。


Adapter

解决:

“这个东西已经存在,但它的接口和我的系统不一样。”

例如:

Noitom SDK ↓ Adapter ↓ MotionInput

可以这样记:

Strategy = 换一种做法 Adapter = 把不一样的东西接进来

4、Factory:工厂模式

现在又出现一个问题:

source_type = xsens

系统怎么创建:

XsensUdpSource

而不是:

AxisBvhSource

这时候就可以使用 Factory。

例如:

std::unique_ptr<MotionInput> createMotionInput(const Config& config) { if (config.source_type == "xsens") return std::make_unique<XsensUdpSource>(); if (config.source_type == "axis_bvh") return std::make_unique<AxisBvhSource>(); if (config.source_type == "noitom") return std::make_unique<NoitomSource>(); return nullptr; }

于是主程序只需要:

auto input = createMotionInput(config);

而不需要自己判断。


5、Observer:观察者模式

这个在你的项目里可以简单理解成:

一个模块产生状态变化,其他模块希望知道。

比如:

MotionInput │ ├── 新帧到达 ├── 数据源断开 ├── 延迟异常 └── 丢帧 │ ↓ Observer / \ ↓ ↓ statistics diagnostics

例如:

MotionInput ↓ 统计模块 ↓ P50/P95/P99 latency lost frames queue waterline

或者:

MotionInput ↓ Diagnostics ↓ 设备状态 连接状态 错误信息

你现在不需要把 Observer 写得很复杂。

先理解:

一个对象发生变化 → 通知关注它的其他对象。

MotionInput (抽象接口) ↑ ┌───────────────┼───────────────┐ │ │ │ Xsens Axis Noitom Strategy Strategy Strategy │ │ │ └───────────────┼───────────────┘ ↓ MotionFrame ↑ Adapter (数据格式转换) ↑ Factory (根据配置创建对象) ↓ GMR ↓ Robot Target ↓ Observer ┌─────────┴─────────┐ ↓ ↓ Statistics Diagnostics
概念解决的问题
OOP把复杂系统拆成对象
Interface规定统一能力
Strategy多种实现可以替换
Adapter不兼容接口转换
Factory负责对象创建
Observer状态变化通知其他模块
RAII/智能指针管理资源和生命周期
测试人员 │ ↓ Web 浏览器 │ HTTP / SocketIO │ ↓ Flask │ ┌────────────┼────────────┐ ↓ ↓ ↓ 测试控制 状态查询 实时通知 │ │ │ └────────────┼────────────┘ ↓ Python测试程序 │ ┌────────┼────────┐ ↓ ↓ ↓ ROS2 CAN EtherCAT │ ↓ 机器人 │ ↓ 测试结果 │ ↓ SQLite │ ↓ 测试报告/PDF

所以这里其实是:

Web → 测试程序 → 机器人 → 数据库 → 报告


6、为什么还需要 SocketIO?

这里是一个很重要的区别。

普通 HTTP:

浏览器 ──请求──→ Flask 浏览器 ←─响应─── Flask

例如:

GET /status

浏览器主动问:

现在怎么样?


但是机器人测试经常需要:

服务器主动告诉浏览器发生了什么。

例如:

机器人测试开始 ↓ 10% ↓ 20% ↓ 30% ↓ 发现异常 ↓ 测试停止

如果全部用 HTTP:

浏览器不断 GET /status 浏览器不断 GET /status 浏览器不断 GET /status

比较麻烦。

SocketIO 可以建立实时事件通信:

浏览器 ↕ SocketIO ↕ Python

服务器可以主动发送:

test_started progress test_error test_finished
浏览器 │ SocketIO连接 │ ↓ Flask │ ↓ Python测试框架 │ ┌───────────┼───────────┐ ↓ ↓ ↓ ROS2 CAN EtherCAT │ │ │ └───────────┼───────────┘ ↓ DUT ↓ Result

例如测试开始:

浏览器 │ │ start_test ↓ Python │ ↓ 执行测试 │ ├── 读取设备 ├── 发送命令 ├── 检查反馈 ├── 统计数据 └── 判断Pass/Fail │ ↓ SocketIO │ ↓ 浏览器显示
测试任务 ↓ 执行机器人测试 ↓ 采集数据 ↓ 判断结果 ↓ 保存 SQLite ↓ 生成日志 ↓ 生成报告

这就比单纯:

python test.py

完整很多。


7、pytest

1. assert

assert result == expected

2. fixture

给测试准备环境:

测试开始 ↓ 准备设备/数据 ↓ 执行测试 ↓ 清理

3. parametrize

同一个测试跑多组参数:

速度 0.1 速度 0.5 速度 1.0 速度 2.0

不用写四个测试函数。

4. mock

当真实硬件不存在时:

真实 EtherCAT ↓ Mock ↓ 模拟设备

这样可以先测试软件逻辑。

现在把这一块全部串起来:

Web浏览器 │ HTTP / SocketIO │ ↓ Flask │ ↓ Python测试框架 │ ┌──────────┼──────────┐ ↓ ↓ ↓ ROS2 CAN EtherCAT │ │ │ └──────────┼──────────┘ ↓ 机器人 │ ↓ 测试数据 │ ┌──────────┴──────────┐ ↓ ↓ SQLite 日志 │ │ └──────────┬──────────┘ ↓ 测试报告

而 pytest 负责:

Python代码 ↓ pytest ↓ ┌────────┼────────┐ ↓ ↓ ↓ 单元测试 集成测试 回归测试

PyInstaller 则负责:

Python测试工具 ↓ PyInstaller ↓ 可部署程序
Flask → 提供 Web/API SocketIO → 实时双向通信 SQLite → 保存测试结果 pytest → 自动化测试 PyInstaller → 打包部署

再记一条完整工程链:

Web控制 → Python测试 → 机器人执行 → 数据采集 → SQLite保存 → 日志 → 报告 → PyInstaller部署


8、日志最基本的几个等级

通常会看到:

DEBUG INFO WARN ERROR

可以简单理解成:

DEBUG

调试细节:

received frame id=123 queue size=20

INFO

正常运行信息:

EtherCAT master started Controller activated

WARN

出现异常趋势,但系统还能继续:

packet delay high queue almost full

ERROR

明确出现错误:

device disconnected controller activation failed

你不需要死记日志库。

先记:

不同等级帮助你快速筛选问题。

测试需求 ↓ 测试用例 ↓ 自动化测试程序 ↓ ┌───────────┼───────────┐ ↓ ↓ ↓ ROS2 CAN EtherCAT └───────────┼───────────┘ ↓ 机器人 ↓ 数据 ↓ ┌──────┴──────┐ ↓ ↓ 日志 测试结果 ↓ ↓ └──────┬──────┘ ↓ SQLite ↓ 统计分析 ↓ 测试报告 ↓ 回归测试

这才是完整的:

机器人测试工程。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询