介绍嵌入式C语言中策略模式的基本原理和实现方法

嵌入式技术

1368人已加入

描述

说明

嵌入式系统常常需要对不同的输入采取不同的行为,例如按下按钮后的操作、传感器读数后的处理、接收到的通信数据的解析等等。在这种情况下,策略模式可以提供一种清晰、可扩展的解决方案。本文将介绍嵌入式C语言中策略模式的基本原理和实现方法。

策略模式的实现方式通常包括以下三个部分:

定义策略接口:定义一个抽象的接口或基类,该接口或基类包含了不同策略的通用方法。

实现具体策略:实现不同的策略类,每个策略类都实现了策略接口或基类中定义的方法。

选择策略:在运行时根据需要选择特定的策略对象,并调用其方法。

下面给出几个嵌入式C语言中策略模式的案例:

  • 控制器:一个嵌入式控制器需要根据不同的传感器数据选择不同的控制策略。例如,在温度传感器的值高于某个阈值时,选择降温策略,否则选择加热策略。
  • 数据通信:在嵌入式设备之间的通信中,可能需要选择不同的通信协议或传输方式。例如,在通过RS-232串口进行通信时,需要选择串口通信策略;在通过网络进行通信时,需要选择网络通信策略
  • 显示控制:一个嵌入式显示控制器需要根据不同的显示需求选择不同的显示策略。例如,在显示数字时,需要选择数字显示策略;在显示文本时,需要选择文本显示策略。
  • 定时器:在嵌入式系统中,可能需要定期执行不同的任务。例如,在一个定时器中,可以注册不同的定时任务,每个任务实现不同的功能。
  • 外设控制:在嵌入式系统中,需要对不同的外设进行控制。例如,在驱动一个电机时,需要选择不同的控制方式,如PWM、脉冲计数等。

在这些案例中,策略模式能够提高系统的灵活性和可维护性,使系统能够在运行时根据不同的情况选择不同的算法或行为,同时降低代码的复杂度和耦合度。

上面只是举例的一些,实际在很多场景中用到,即使点亮1个LED灯也可以用得到。

1- 策略模式概述

策略模式是一种对象行为型模式,它定义了一系列的算法,并将每一个算法封装起来,使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户端,从而实现了算法的动态切换。

在嵌入式系统中,策略模式的主要目的是将不同的行为或动作封装在不同的算法对象中,并使得这些算法对象可以在运行时根据需要动态切换。这种设计模式可以让嵌入式系统更加灵活,易于维护和扩展。

2- 嵌入式C语言中的策略模式

嵌入式C语言中的策略模式通常采用函数指针来实现。每个策略被封装在一个函数中,并通过函数指针的形式存储在一个结构体中。客户端代码可以通过修改结构体中的函数指针来切换算法。下面是一个简单的例子:

typedef struct {
    void (*foo)(void);
    void (*bar)(void);
} Strategy;


void foo1(void) {
    // do something
}


void foo2(void) {
    // do something else
}


void bar1(void) {
    // do another thing
}


void bar2(void) {
    // do yet another thing
}


void main(void) {
    Strategy strategy = {foo1, bar1};
    // ...
    strategy.foo();
    strategy.bar();
    // ...
    strategy.foo = foo2;
    strategy.bar = bar2;
    // ...
    strategy.foo();
    strategy.bar();
    // ...
}

在这个例子中,Strategy 结构体包含了两个函数指针 foo 和 bar,分别表示两种不同的算法。在程序运行时,可以通过修改 Strategy 结构体中的函数指针来切换算法。

当然,上面的例子还比较简单,实际中的策略模式往往需要更加复杂的数据结构和算法。下面我们来看一些更加实际的例子。

在嵌入式系统中,常常需要对按键事件进行处理,例如按下按钮后要执行某种操作。不同的按键事件需要执行不同的操作,因此可以将按键事件的处理封装成一个策略模式。

下面是一个例子,我们假设系统中有两个按键,分别为 BUTTON1 和 BUTTON2,按下不同的按键需要执行不同的操作。

首先定义一个策略接口:

typedef void (*ButtonStrategy)(void);

然后定义不同的算法,即按键事件的处理函数:

void Button1Strategy(void) {
    // do something when button 1 is pressed
}


void Button2Strategy(void) {
    // do something when button 2 is pressed
}

接下来定义一个策略表,其中每个元素是一个策略对象:

typedef struct {
    uint8_t button; // 按钮编号
    ButtonStrategy strategy; // 策略函数指针
} ButtonStrategyTable;


static const ButtonStrategyTable buttonStrategies[] = {
    {1, Button1Strategy},
    {2, Button2Strategy},
};

最后,在中断处理函数中根据按键编号查找策略表,并执行相应的策略:

void ButtonInterruptHandler(uint8_t button) {
    for (size_t i = 0; i < sizeof(buttonStrategies) / sizeof(buttonStrategies[0]); i++) {
        if (buttonStrategies[i].button == button) {
            buttonStrategies[i].strategy();
            break;
        }
    }
}

在这个例子中,我们使用一个策略表来存储不同的策略对象,每个对象包含一个按钮编号和一个策略函数指针。当某个按钮被按下时,中断处理函数会根据按钮编号查找策略表,找到相应的策略并执行。

3-传感器数据处理

另一个常见的嵌入式系统应用场景是传感器数据处理。例如,系统中可能需要读取多个传感器的数据,并根据数据的不同进行不同的处理。

我们可以将每个传感器的数据处理封装成一个策略对象,然后使用一个策略表来管理这些对象。

下面是一个例子,我们假设系统中有两个传感器,分别为 SENSOR1 和 SENSOR2,并且它们的数据类型不同,分别为 uint16_t 和 float。对于不同的数据类型,我们需要执行不同的处理操作。

首先定义一个策略接口

typedef void (*SensorStrategy)(void* data);

然后定义不同的算法,即传感器数据的处理函数:

void Sensor1Strategy(void* data) {
    uint16_t sensorData = *(uint16_t*)data;
    // do something with sensorData
}


void Sensor2Strategy(void* data) {
    float sensorData = *(float*)data;
    // do something with sensorData
}

接下来定义一个策略表,其中每个元素是一个策略对象:

typedef struct {
    uint8_t sensor; // 传感器编号
    size_t dataSize; // 数据大小
    SensorStrategy strategy; //
    } SensorStrategyTable;


static const SensorStrategyTable sensorStrategies[] =
 {{1, sizeof(uint16_t), Sensor1Strategy},
{2, sizeof(float), Sensor2Strategy},};

最后,在读取传感器数据时,根据传感器编号查找策略表,并执行相应的策略:

void ReadSensorData(uint8_t sensor, void* data) {
// read sensor data into data buffer
// ...
for (size_t i = 0; i < sizeof(sensorStrategies) / sizeof(sensorStrategies[0]); i++) {
    if (sensorStrategies[i].sensor == sensor) {
        sensorStrategies[i].strategy(data);
        break;
    }
}

在这个例子中,我们使用一个策略表来存储不同的策略对象,每个对象包含一个传感器编号、数据大小和一个策略函数指针。当某个传感器的数据被读取时,函数会根据传感器编号查找策略表,找到相应的策略并执行。

总结一下,嵌入式系统中使用策略模式可以将复杂的业务逻辑分解成多个小的算法,每个算法都可以单独开发、测试和维护。通过使用策略表,我们可以方便地管理这些算法,并在运行时动态地选择合适的算法。这种设计方式可以提高代码的可读性、可维护性和可扩展性,是嵌入式系统开发中常用的设计模式之一。

下面我们来看一个更加具体的例子。假设我们正在开发一个智能家居系统,其中有多个设备(如灯光、窗帘、温度传感器等),每个设备都有多种操作模式(如开关、调节亮度、调节温度等),我们需要根据用户的输入来选择相应的操作模式。这时候就可以使用策略模式。

首先,我们定义一个设备基类和一个操作模式基类,用于后续的派生类实现:

typedef struct _Device Device;
typedef struct _Mode Mode;


struct _Device {
    uint8_t id;
    char* name;
    Mode* mode;
};


struct _Mode {
    char* name;
    void (*Do)(Device*);
};
接下来,我们定义几个设备派生类和操作模式派生类
typedef struct _Light Light;
typedef struct _Curtain Curtain;
typedef struct _Thermostat Thermostat;


struct _Light {
    Device base;
    bool state;
    uint8_t brightness;
};


struct _Curtain {
    Device base;
    bool state;
    uint8_t position;
};


struct _Thermostat {
    Device base;
    float temperature;
    Mode* modes[3];
};


void Light_On(Device* device) {
    Light* light = (Light*)device;
    light->state = true;
    printf("%s turned on.\\n", light->base.name);
}


void Light_Off(Device* device) {
    Light* light = (Light*)device;
    light->state = false;
    printf("%s turned off.\\n", light->base.name);
}


void Light_Dim(Device* device) {
    Light* light = (Light*)device;
    light->brightness--;
    printf("%s dimmed to %d%% brightness.\\n", light->base.name, light->brightness);
}


void Light_Brighten(Device* device) {
    Light* light = (Light*)device;
    light->brightness++;
    printf("%s brightened to %d%% brightness.\\n", light->base.name, light->brightness);
}


void Curtain_Open(Device* device) {
    Curtain* curtain = (Curtain*)device;
    curtain->state = true;
    printf("%s opened.\\n", curtain->base.name);
}


void Curtain_Close(Device* device) {
    Curtain* curtain = (Curtain*)device;
    curtain->state = false;
    printf("%s closed.\\n", curtain->base.name);
}


void Curtain_Up(Device* device) {
    Curtain* curtain = (Curtain*)device;
    curtain->position++;
    printf("%s raised to %d%% position.\\n", curtain->base.name, curtain->position);
}


void Curtain_Down(Device* device) {
    Curtain* curtain = (Curtain*)device;
    curtain->position--;
    printf("%s lowered to %d%% position.\\n", curtain->base.name, curtain->position);
}


void Thermostat_Cool(Device* device) {
    Thermostat* thermostat = (Thermostat*)device;
    thermostat->temperature--;
    printf("%s cooled to %.1f°C.\\n", thermostat->base.name, thermostat->temperature);
}


void Thermostat_Heat(Device* device) {
    Thermostat* thermostat = (Thermostat*)device;
    thermostat->temperature++;
............
..........
...
...

最后,我们在主函数中使用策略模式进行设备控制:

int main() {
    Light light = {
        .base = { .id = 1, .name = "Living room light", .mode = NULL },
        .state = false,
        .brightness = 100
    };
    Curtain curtain = {
        .base = { .id = 2, .name = "Bedroom curtain", .mode = NULL },
        .state = false,
        .position = 0
    };
    Thermostat thermostat = {
        .base = { .id = 3, .name = "Kitchen thermostat", .mode = NULL },
        .temperature = 20.0
    };
    Light_On(&(light.base));
    Light_Dim(&(light.base));
    Curtain_Open(&(curtain.base));
    Curtain_Up(&(curtain.base));
    Thermostat_SetModes(&thermostat);
    thermostat.mode = &(thermostat.modes[0]);
    thermostat.mode->Do(&(thermostat.base));
    return 0;
}

在这个例子中,我们首先定义了一个设备基类和一个操作模式基类,并定义了几个设备派生类和操作模式派生类。然后,我们为每个设备添加了一个指向操作模式的指针,以便在运行时动态切换操作模式。最后,我们在主函数中使用策略模式进行设备控制,首先设置了每个设备的初始状态,然后依次执行了多种操作模式。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分