嵌入式开发设计模式——ForEach

问题

多个相同的实例对象,其中一部分需要执行某个相同操作。例如有4路天线(使用tx代替),在不同应用中可能选择其中某几路天线,执行“上电-发射-下电”的操作序列。

解决方案

在面向对象的语言中可以实例化多个对象,对需要执行操作的对象分别调用其方法即可。但是在面向过程的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
typedef enum {
TX_CH_1,
TX_CH_2,
TX_CH_3
} tx_channel_t;

void power_on_action(tx_channel_t tx) {
switch (tx) {
case TX_CH_1: /* do actions for tx1 */ break;
case TX_CH_2: /* do actions for tx2 */ break;
case TX_CH_3: /* do actions for tx3 */ break;
}
}

void tx_power_on(bool tx1, bool tx2, bool tx3) {
if (tx1) {
power_on_action(TX_CH_1);
}
if (tx2) {
power_on_action(TX_CH_2);
}
if (tx3) {
power_on_action(TX_CH_3);
}
}

当对象少于4个时,这种方式很直接高效,但是当对象多于4个时就存在大量重复代码,并且函数参数过长,需要重构设计了。
考虑到某个对象的参数只需要Yes/No两种值,可以想到使用1 bit代表一个对象。如此一个Byte即可表示8个对象。

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
void power_on(uint8_t tx_mask) {
for (uint8_t i = 0; i < 8; i++) {
uint8_t sel = 1 << i;
if (0 != (tx_mask & sel)) {
power_on_action(i);
}
}
}

void power_off(uint8_t tx_mask) {
for (uint8_t i = 0; i < 8; i++) {
uint8_t sel = 1 << i;
if (0 != (tx_mask & sel)) {
power_off_action(i);
}
}
}

void send(uint8_t tx_mask) {
for (uint8_t i = 0; i < 8; i++) {
uint8_t sel = 1 << i;
if (0 != (tx_mask & sel)) {
send_action(i);
}
}
}

观察操作函数结构均相同,再进一步重构,将重复的结构抽出来,将操作转为参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static inline void for_each_tx(uint8_t mask, void (*action)(tx_channel_t tx)) {
for (uint8_t i = 0; i < 8; i++) {
if (0 != (tx_mask & (1 << i))) {
action(i);
}
}
}

void power_on(uint8_t tx_mask) {
for_each_tx(tx_mask, power_on_action);
}

void power_off(uint8_t tx_mask) {
for_each_tx(tx_mask, power_off_action);
}

void send(uint8_t tx_mask) {
for_each_tx(tx_mask, send_action);
}

回顾此设计模式,核心原则仍是DRY(Don’t Repeat Yourself),只不过在这里Repeat的不仅是操作,还包括代码结构。