[PICO][Adv]命令模式

Raspberry Pi Pico 2 命令模式

命令模式(Command Pattern)是一种行为设计模式,它将请求封装为对象,从而使你可以用不同的请求对客户进行参数化,支持请求排队、记录日志以及撤销操作。在 Raspberry Pi Pico 2 编程中,命令模式可以帮助你将复杂的操作分解为独立的命令对象,从而提高代码的可维护性、可扩展性和复用性。

本文将分别使用 Arduino 风格(基于 Arduino-Pico 核心)和 C/C++ 风格(基于官方 Pico SDK)来详细介绍命令模式的概念、结构以及实际应用案例。


1. 什么是命令模式?

命令模式的核心思想是将“做什么”和“谁来做”解耦。通过将操作封装为对象,你可以在不同的上下文中重用这些操作,甚至可以将其存储在队列中以便稍后执行,或者实现撤销/重做功能。

1.1 命令模式的组成部分

角色 描述
命令接口(Command) 定义执行操作的统一接口(通常为 execute() 方法)。
具体命令(Concrete Command) 实现命令接口,封装具体的操作和对接收者的调用。
接收者(Receiver) 真正执行操作的对象,知道如何完成具体工作。
调用者(Invoker) 持有命令对象,在适当的时候调用命令的 execute() 方法。
客户端(Client) 创建具体命令对象并设置其接收者,将命令与调用者关联。

1.2 命令模式的优势

优势 说明
解耦 将请求的发起者与请求的执行者分离,降低系统耦合度。
可扩展性 新增命令无需修改已有代码,符合开闭原则。
组合能力 可以将多个命令组合成宏命令(复合命令)。
支持撤销/重做 通过存储命令历史记录,可以轻松实现撤销操作。
任务队列 可将命令对象放入队列,实现任务调度。

2. 命令模式的基本结构

2.1 Arduino 风格实现

在 Arduino 风格中,我们使用 C++ 的类继承机制来实现命令模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// 1. 命令接口(抽象类)
class Command {
public:
virtual void execute() = 0;
virtual void undo() = 0; // 可选:支持撤销
};

// 2. 接收者:LED
class Led {
private:
int pin;
public:
Led(int pin) : pin(pin) {
pinMode(pin, OUTPUT);
}

void on() {
digitalWrite(pin, HIGH);
}

void off() {
digitalWrite(pin, LOW);
}

bool getState() {
return digitalRead(pin);
}
};

// 3. 具体命令:打开LED
class LedOnCommand : public Command {
private:
Led* led;
public:
LedOnCommand(Led* led) : led(led) {}

void execute() override {
led->on();
}

void undo() override {
led->off();
}
};

// 4. 具体命令:关闭LED
class LedOffCommand : public Command {
private:
Led* led;
public:
LedOffCommand(Led* led) : led(led) {}

void execute() override {
led->off();
}

void undo() override {
led->on();
}
};

// 5. 调用者:按钮
class Button {
private:
Command* command;
String name;
public:
Button(String name) : name(name), command(nullptr) {}

void setCommand(Command* cmd) {
command = cmd;
}

void press() {
if (command) {
Serial.print(name);
Serial.println(" pressed");
command->execute();
}
}
};

// 6. 客户端使用
Led led(25); // 使用板载 LED
LedOnCommand ledOn(&led);
LedOffCommand ledOff(&led);
Button btn1("Button 1");
Button btn2("Button 2");

void setup() {
Serial.begin(115200);
btn1.setCommand(&ledOn);
btn2.setCommand(&ledOff);
}

void loop() {
// 模拟按钮按下
btn1.press();
delay(1000);
btn2.press();
delay(1000);
}

2.2 C/C++ 风格实现

在 Pico SDK(C 语言)中,由于没有类机制,我们使用函数指针结构体来模拟面向对象的多态行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#include "pico/stdlib.h"
#include <stdio.h>

// 1. 命令结构体(包含函数指针)
typedef struct Command Command;
struct Command {
void (*execute)(Command* self);
void (*undo)(Command* self);
};

// 2. 接收者:LED
typedef struct {
uint pin;
} Led;

void led_init(Led* led, uint pin) {
led->pin = pin;
gpio_init(pin);
gpio_set_dir(pin, GPIO_OUT);
}

void led_on(Led* led) {
gpio_put(led->pin, 1);
}

void led_off(Led* led) {
gpio_put(led->pin, 0);
}

// 3. 具体命令:打开LED
typedef struct {
Command base; // 继承基类
Led* led;
} LedOnCommand;

void led_on_command_execute(Command* self) {
LedOnCommand* cmd = (LedOnCommand*)self;
led_on(cmd->led);
}

void led_on_command_undo(Command* self) {
LedOnCommand* cmd = (LedOnCommand*)self;
led_off(cmd->led);
}

void led_on_command_init(LedOnCommand* cmd, Led* led) {
cmd->base.execute = led_on_command_execute;
cmd->base.undo = led_on_command_undo;
cmd->led = led;
}

// 4. 具体命令:关闭LED
typedef struct {
Command base;
Led* led;
} LedOffCommand;

void led_off_command_execute(Command* self) {
LedOffCommand* cmd = (LedOffCommand*)self;
led_off(cmd->led);
}

void led_off_command_undo(Command* self) {
LedOffCommand* cmd = (LedOffCommand*)self;
led_on(cmd->led);
}

void led_off_command_init(LedOffCommand* cmd, Led* led) {
cmd->base.execute = led_off_command_execute;
cmd->base.undo = led_off_command_undo;
cmd->led = led;
}

// 5. 调用者:按钮
typedef struct {
Command* command;
const char* name;
} Button;

void button_init(Button* btn, const char* name) {
btn->command = NULL;
btn->name = name;
}

void button_set_command(Button* btn, Command* cmd) {
btn->command = cmd;
}

void button_press(Button* btn) {
if (btn->command) {
printf("%s pressed\n", btn->name);
btn->command->execute(btn->command);
}
}

// 6. 客户端使用
int main() {
stdio_init_all();

Led led;
led_init(&led, 25); // 板载 LED

LedOnCommand ledOnCmd;
led_on_command_init(&ledOnCmd, &led);

LedOffCommand ledOffCmd;
led_off_command_init(&ledOffCmd, &led);

Button btn1, btn2;
button_init(&btn1, "Button 1");
button_init(&btn2, "Button 2");
button_set_command(&btn1, (Command*)&ledOnCmd);
button_set_command(&btn2, (Command*)&ledOffCmd);

while (true) {
button_press(&btn1);
sleep_ms(1000);
button_press(&btn2);
sleep_ms(1000);
}
return 0;
}

提示:在 C 语言中实现命令模式比 C++ 繁琐,但核心思想相同——将操作封装为对象(结构体 + 函数指针),从而实现调用者与执行者的解耦。


3. 实际应用场景

命令模式在 Pico 2 项目中有广泛的应用,尤其是在需要处理多个复杂操作、支持撤销/重做、或需要任务队列的场景中。

3.1 场景一:遥控器控制多个设备

假设你有一个遥控器,可以控制多个设备(如 LED、风扇、蜂鸣器等)。你可以为每个设备创建命令对象,并将这些命令绑定到遥控器的按钮上。

硬件连接

  • LED:GP25(板载)
  • 风扇(模拟):GP0 接 LED 模拟风扇
  • 蜂鸣器:GP1 接蜂鸣器

Arduino 风格实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// 接收者:风扇
class Fan {
private:
int pin;
public:
Fan(int pin) : pin(pin) {
pinMode(pin, OUTPUT);
digitalWrite(pin, LOW);
}
void on() { digitalWrite(pin, HIGH); }
void off() { digitalWrite(pin, LOW); }
};

// 接收者:蜂鸣器
class Buzzer {
private:
int pin;
public:
Buzzer(int pin) : pin(pin) {
pinMode(pin, OUTPUT);
digitalWrite(pin, LOW);
}
void beep() {
digitalWrite(pin, HIGH);
delay(100);
digitalWrite(pin, LOW);
}
};

// 具体命令:风扇开
class FanOnCommand : public Command {
private:
Fan* fan;
public:
FanOnCommand(Fan* fan) : fan(fan) {}
void execute() override { fan->on(); }
void undo() override { fan->off(); }
};

// 具体命令:风扇关
class FanOffCommand : public Command {
private:
Fan* fan;
public:
FanOffCommand(Fan* fan) : fan(fan) {}
void execute() override { fan->off(); }
void undo() override { fan->on(); }
};

// 具体命令:蜂鸣器响一下
class BuzzerBeepCommand : public Command {
private:
Buzzer* buzzer;
public:
BuzzerBeepCommand(Buzzer* buzzer) : buzzer(buzzer) {}
void execute() override { buzzer->beep(); }
void undo() override { /* 无法撤销蜂鸣 */ }
};

// 遥控器(支持多个按钮)
class RemoteControl {
private:
Command* buttons[4]; // 支持 4 个按钮
public:
RemoteControl() {
for (int i = 0; i < 4; i++) buttons[i] = nullptr;
}

void setCommand(int slot, Command* cmd) {
buttons[slot] = cmd;
}

void pressButton(int slot) {
if (buttons[slot]) {
Serial.print("Button ");
Serial.print(slot);
Serial.println(" pressed");
buttons[slot]->execute();
}
}
};

// 客户端使用
Led led(25);
Fan fan(0);
Buzzer buzzer(1);

LedOnCommand ledOn(&led);
LedOffCommand ledOff(&led);
FanOnCommand fanOn(&fan);
FanOffCommand fanOff(&fan);
BuzzerBeepCommand buzzerBeep(&buzzer);

RemoteControl remote;

void setup() {
Serial.begin(115200);
remote.setCommand(0, &ledOn);
remote.setCommand(1, &ledOff);
remote.setCommand(2, &fanOn);
remote.setCommand(3, &buzzerBeep);
}

void loop() {
// 模拟按下不同按钮
for (int i = 0; i < 4; i++) {
remote.pressButton(i);
delay(1500);
}
}

3.2 场景二:支持撤销的操作队列

命令模式天然支持撤销功能。我们可以存储命令历史记录,实现撤销操作。

Arduino 风格实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// 简单命令历史记录(最多存储 10 条)
class CommandHistory {
private:
Command* history[10];
int index;
public:
CommandHistory() : index(0) {
for (int i = 0; i < 10; i++) history[i] = nullptr;
}

void push(Command* cmd) {
if (index < 10) {
history[index++] = cmd;
}
}

Command* pop() {
if (index > 0) {
return history[--index];
}
return nullptr;
}
};

CommandHistory history;

// 包装命令,自动记录历史
class RecordableCommand : public Command {
private:
Command* command;
public:
RecordableCommand(Command* cmd) : command(cmd) {}

void execute() override {
command->execute();
history.push(this); // 记录到历史
}

void undo() override {
command->undo();
}
};

// 使用示例(在 setup 中)
RecordableCommand recLedOn(&ledOn);
RecordableCommand recLedOff(&ledOff);

void loop() {
// 执行一些命令
recLedOn.execute(); // 打开 LED
delay(1000);
recLedOff.execute(); // 关闭 LED
delay(1000);

// 撤销上一步操作(关闭 LED 的撤销是打开)
Command* lastCmd = history.pop();
if (lastCmd) {
Serial.println("Undo last command");
lastCmd->undo();
delay(1000);
}
}

3.3 场景三:宏命令(组合命令)

宏命令可以组合多个命令,实现一键执行一系列操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 宏命令:包含多个子命令
class MacroCommand : public Command {
private:
Command** commands;
int count;
public:
MacroCommand(Command** cmds, int cnt) : commands(cmds), count(cnt) {}

void execute() override {
for (int i = 0; i < count; i++) {
commands[i]->execute();
}
}

void undo() override {
// 逆序撤销
for (int i = count - 1; i >= 0; i--) {
commands[i]->undo();
}
}
};

// 使用示例
Command* partyCommands[] = { &ledOn, &fanOn, &buzzerBeep };
MacroCommand partyMode(partyCommands, 3);

// 按下一键派对按钮
partyMode.execute();

4. 命令模式与状态机的对比

命令模式和状态机是两种不同的设计模式,各有适用场景:

特性 命令模式 状态机
关注点 封装操作(动作) 管理状态转换
核心结构 命令对象、调用者、接收者 状态枚举、转换逻辑
典型应用 遥控器、任务队列、撤销功能 交通灯、自动门、协议解析
可扩展性 新增命令无需修改现有代码 新增状态需修改状态机逻辑
组合能力 支持宏命令、命令队列 支持层次化状态机

两者可以结合使用:例如,状态机的每个状态转换可以触发一个命令对象执行。


5. 总结

命令模式是一种强大的设计模式,可以帮助你将复杂的操作封装为独立的命令对象,从而提高代码的可维护性和扩展性。通过将“做什么”和“谁来做”解耦,命令模式使得你可以在不同的上下文中重用这些操作,甚至可以将其存储在队列中以便稍后执行。

要点 说明
核心价值 解耦请求发起者与请求执行者
适用场景 遥控器、任务队列、撤销/重做、宏命令
Pico 2 实现 Arduino 风格使用 C++ 类;C/C++ 风格使用结构体 + 函数指针
扩展能力 可轻松添加日志记录、命令排队、异步执行等功能

6. 练习与拓展

  • 练习 1:为遥控器示例添加一个“宏录制”功能:记录用户按下的按钮序列,然后一键回放。
  • 练习 2:结合之前的事件驱动编程,使用命令模式实现一个串口命令解析器:当收到 "LED ON" 时执行对应的命令对象。
  • 练习 3:实现一个支持多级撤销的命令历史(不限深度),可循环撤销。
  • 练习 4:在 C/C++ 风格中,尝试用函数指针数组实现一个简单的命令分发器,而不需要为每个命令定义独立的结构体。

通过掌握命令模式,你将能够编写出更灵活、可扩展的 Pico 2 应用程序,轻松应对复杂的控制逻辑和设备管理场景。


[PICO][Adv]命令模式
https://ka5fxt.cn/2026/03/30/PICO-Adv-命令模式/
发布于
2026年3月30日
许可协议