
The first part of the series of articles about bufferevent introduces its use of the Strategy Pattern, which implements polymorphic behavior through the struct bufferevent_ops.
The Strategy Pattern Strategy Pattern
The Strategy Pattern is a behavioral design pattern that defines a family of algorithms, encapsulates each one, and makes them interchangeable. This pattern allows the algorithm to vary independently from the clients that use it.
The Strategy Pattern consists of the following key components:
-
Context: Maintains a reference to a Strategy object and uses it to execute an algorithm.Context: Maintains a reference to a Strategy object and uses it to execute an algorithm.
-
Strategy: An interface common to all supported algorithms or behaviors.Strategy: An interface common to all supported algorithms or behaviors.
-
Concrete Strategies: Classes that implement the Strategy interface, providing specific algorithm implementations.Concrete Strategies: Classes that implement the Strategy interface, providing specific algorithm implementations.
In embedded systems, this pattern is particularly useful for the following scenarios:
-
Switching communication protocols: Dynamically switch between UART, I2C, and SPI.Switching communication protocols: Dynamically switch between UART, I2C, and SPI.
-
Sensor data processing: Apply different processing strategies for various sensor types, such as temperature, humidity, or light sensors.Sensor data processing: Apply different processing strategies for various sensor types, such as temperature, humidity, or light sensors.
-
Power management: Change power consumption strategies based on the power source (e.g., battery or external power).Power management: Change power consumption strategies based on the power source (e.g., battery or external power).
-
Control algorithms: Dynamically switch between different control algorithms like PID or fuzzy logic control.Control algorithms: Dynamically switch between different control algorithms like PID or fuzzy logic control.
The following table compares designs with and without the Strategy Pattern:

Embedded Application Example
This example demonstrates the use of different strategies to process data from different sensor types (temperature and humidity).
In C, function pointers can be used to represent different strategies to implement the Strategy Pattern.
1. Strategy Interface and Concrete Strategies
The strategy interface is a function pointer type. Each concrete strategy is a function that matches this signature.
// Strategy Interface typedef void (*SensorStrategy)(void* data);
// Concrete Strategy: Temperature Sensor void temperature_strategy(void* data) { float* temp = (float*)data; printf(“[C] Processing temperature: %.1fC -> Calibrated: %.1fC\n”, *temp, *temp + 0.5f); } // Concrete Strategy: Humidity Sensor void humidity_strategy(void* data) { int* humidity = (int*)data; printf(“[C] Processing humidity: %d%% -> Adjusted: %d%%\n”, *humidity, *humidity + 2); }
2. Context
The context contains pointers to the current strategy and the data to be processed.
<span><span>// Context: Sensor Processor typedef struct { SensorStrategy strategy; void* sensor_data; } SensorProcessor;</span></span>
3. Complete Example in C
<span><span>#include</span><span> <stdio.h> </span></span>
<span><span>// Strategy Interface typedef void (*SensorStrategy)(void* data); </span></span>
<span><span>// Concrete Strategy: Temperature Sensor void temperature_strategy(void* data) { float* temp = (float*)data; printf("[C] Processing temperature: %.1fC -> Calibrated: %.1fC\n", *temp, *temp + 0.5f); } // Concrete Strategy: Humidity Sensor void humidity_strategy(void* data) { int* humidity = (int*)data; printf("[C] Processing humidity: %d%% -> Adjusted: %d%%\n", *humidity, *humidity + 2); } // Context: Sensor Processor typedef struct { SensorStrategy strategy; void* sensor_data; } SensorProcessor; void process_sensor(SensorProcessor* processor) { processor->strategy(processor->sensor_data); } int main(void) { printf("\n--- Strategy Pattern Demo ---\n"); float temp_data = 25.3f; int humidity_data = 45; SensorProcessor temp_processor = {temperature_strategy, &temp_data}; SensorProcessor hum_processor = {humidity_strategy, &humidity_data}; process_sensor(&temp_processor); process_sensor(&hum_processor); return 0; }</span></span>
In C++, the pattern is typically implemented using a generic interface (abstract base class) and concrete classes that inherit from it.
<span><span>#include</span><span> <iostream> #include <memory> </span></span>
<span><span>// Strategy Interface class SensorStrategy { public: virtual void process() = 0; virtual ~SensorStrategy() = default; }; </span></span>
<span><span>// Concrete Strategy: Temperature class TemperatureStrategy : public SensorStrategy { public: explicit TemperatureStrategy(float temp) : temp_(temp) {} void process() override { std::cout << "Processing temperature: " << temp_ << "C -> Calibrated: " << temp_ + 0.5f << "C\n"; } private: float temp_; }; </span></span>
<span><span>// Concrete Strategy: Humidity class HumidityStrategy : public SensorStrategy { public: explicit HumidityStrategy(int humidity) : humidity_(humidity) {} void process() override { std::cout << "Processing humidity: " << humidity_ << "% -> Adjusted: " << humidity_ + 2 << "%\n"; } private: int humidity_; };</span></span>
<span><span> // Context: Sensor Processor class SensorProcessor { public: void set_strategy(std::unique_ptr<SensorStrategy> strategy) { strategy_ = std::move(strategy); } void process_sensor() { if(strategy_) strategy_->process(); } private: std::unique_ptr<SensorStrategy> strategy_; }; int main(void) { std::cout << "\n--- Strategy Pattern Demo ---\n"; SensorProcessor processor; processor.set_strategy(std::make_unique<TemperatureStrategy>(25.3f)); processor.process_sensor(); processor.set_strategy(std::make_unique<HumidityStrategy>(45)); processor.process_sensor(); return 0; }</span></span>
