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
| class Command { public: virtual void execute() = 0; virtual void undo() = 0; };
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); } };
class LedOnCommand : public Command { private: Led* led; public: LedOnCommand(Led* led) : led(led) {} void execute() override { led->on(); } void undo() override { led->off(); } };
class LedOffCommand : public Command { private: Led* led; public: LedOffCommand(Led* led) : led(led) {} void execute() override { led->off(); } void undo() override { led->on(); } };
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(); } } };
Led led(25); 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>
typedef struct Command Command; struct Command { void (*execute)(Command* self); void (*undo)(Command* self); };
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); }
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; }
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; }
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); } }
int main() { stdio_init_all(); Led led; led_init(&led, 25); 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]; 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
| 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(); } };
RecordableCommand recLedOn(&ledOn); RecordableCommand recLedOff(&ledOff);
void loop() { recLedOn.execute(); delay(1000); recLedOff.execute(); delay(1000); 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 应用程序,轻松应对复杂的控制逻辑和设备管理场景。