Fundamentals 12 min read

When to Replace if‑else with Strategy Pattern in Embedded Systems

This article compares traditional if‑else/switch‑case branching with the Strategy design pattern in embedded development, illustrating their differences through analogies and code examples, and explains when to adopt Strategy for better extensibility, lower coupling, and maintainability while noting scenarios where simple branching remains preferable.

IT Services Circle
IT Services Circle
IT Services Circle
When to Replace if‑else with Strategy Pattern in Embedded Systems

Preface

In embedded development, if‑else (or switch‑case) is widely used, but as conditions increase the code becomes highly coupled, forming a "spaghetti" structure. The problem is not the branch itself but its procedural nature leading to tight coupling.

Conclusion: Branching and Strategy pattern are not about superiority; they serve different needs. Strategy focuses on “who does it”, decoupling “what to do” from “how to do it”, allowing each branch to use the same interface and only modify strategy modules.

Illustration

Analogy: if‑else is like a one‑piece screwdriver; changing the screw head requires a whole new tool. Strategy is like an electric screwdriver with interchangeable bits.

if‑else mode: integrated screwdriver

To change a screw head you must replace the whole screwdriver; if you don’t have the required type you cannot work.

if (screw_type == PHILLIPS) {
    use_phillips_screwdriver();
}
else if (screw_type == SLOT) {
    use_slot_screwdriver(); // if this else if is missing, it fails
}

Strategy mode: electric screwdriver

The main unit is stable; each bit represents a different strategy.

The main program only holds the driver and issues a “turn” command.

The actual screw type depends on which bit is attached.

Adding a new screw type only requires a new bit, not a new driver.

Example

if‑else limitation

Example with motor control using multiple if‑else branches.

typedef enum {
    MOTOR_TYPE_DC,
    MOTOR_TYPE_STEPPER,
    MOTOR_TYPE_SERVO
} motor_type_t;

void motor_control(motor_type_t type, void *args) {
    if (type == MOTOR_TYPE_DC) {
        uint8_t percent = *((uint8_t*)args);
        dc_motor_set_speed(percent);
    } else if (type == MOTOR_TYPE_STEPPER) {
        int32_t steps = *((int32_t*)args);
        stepper_motor_step(steps);
    } else if (type == MOTOR_TYPE_SERVO) {
        uint8_t degree = *((uint8_t*)args);
        servo_motor_set_angle(degree);
    }
}

Problems: rigid extension (violates Open/Closed), high coupling, poor maintainability.

Strategy solution

Define a strategy interface and implement each motor type as a separate strategy.

// motor_strategy.h
typedef void (*control_func_t)(struct MotorController* controller, void* args);
typedef struct MotorController {
    control_func_t control;
    void* device_data;
} motor_controller_t;

Implement concrete strategies, e.g., DC motor.

// dc_motor.c
typedef struct {
    TIM_HandleTypeDef* pwm_tim;
    uint32_t pwm_channel;
} dc_motor_data_t;

static void dc_motor_control(motor_controller_t* controller, void* args) {
    uint8_t percent = *((uint8_t*)args);
    dc_motor_data_t* data = (dc_motor_data_t*)(controller->device_data);
    uint32_t ccr = (percent * (data->pwm_tim->Init.Period)) / 100;
    __HAL_TIM_SET_COMPARE(data->pwm_tim, data->pwm_channel, ccr);
}

void dc_motor_init(motor_controller_t* controller, TIM_HandleTypeDef* pwm_tim, uint32_t pwm_channel) {
    dc_motor_data_t* data = malloc(sizeof(dc_motor_data_t));
    data->pwm_tim = pwm_tim;
    data->pwm_channel = pwm_channel;
    controller->control = dc_motor_control;
    controller->device_data = data;
}

Use the strategy:

motor_controller_t dc_motor;
motor_controller_t stepper_motor;

int main() {
    // Initialize strategies
    dc_motor_init(&dc_motor, &htim1, TIM_CHANNEL_1);
    stepper_motor_init(&stepper_motor, GPIOA, GPIO_PIN_5, GPIOA, GPIO_PIN_6);

    uint8_t dc_speed = 50;
    int32_t stepper_steps = 100;

    while (1) {
        motor_control(&dc_motor, &dc_speed);   // DC motor strategy
        HAL_Delay(1000);
        motor_control(&stepper_motor, &stepper_steps); // Stepper motor strategy
        HAL_Delay(1000);
    }
}

Advantages of Strategy

Open/Closed Principle : Adding new motor types only requires a new strategy module.

Low Coupling : Each motor logic is encapsulated and can be tested independently.

Extensibility : New features do not affect existing code.

Clear Code : Single responsibility per module improves readability and maintenance.

Summary

When to use Strategy

Scenario

Recommended

Reason

System needs to switch among multiple algorithms or strategies

Strategy pattern

Better extensibility and maintainability

Complex logic with many conditions needs clear separation

Strategy pattern

Reduces coupling, improves readability

Algorithms may change independently or require independent testing

Strategy pattern

Facilitates unit testing and modular development

Simple, stable logic that rarely changes

if‑else

Direct, minimal overhead

Performance‑critical scenarios

if‑else

Avoids function‑pointer overhead

Core differences

Aspect

if‑else / switch‑case

Strategy pattern

Design philosophy

Procedural: focus on “how to do”

Object‑oriented: focus on “who does it”, decoupling “what” from “how”

Code structure

Vertical growth: more branches inside a function

Horizontal growth: add new strategy classes without touching existing code

Open/Closed

Violated: must modify existing code

Observed: add new modules only

Coupling

High

Low

Readability

Poor with many branches

Good, each strategy is single‑purpose

Maintainability

Low

High

Performance overhead

None

One function‑pointer call (negligible in most embedded apps)

Memory usage

Low

Slightly higher (strategy objects)

Suitable scenarios

Simple, stable logic

Frequent extensions, multiple algorithms

Some Advice

Don’t overuse Strategy

If there is only one motor type and no future extensions, a simple function call is best. Follow the KISS principle.

Gradual adoption

When you see repeated if (device_type == A) checks and need to add new device types, consider Strategy.

Recommended use cases

System needs to switch among multiple algorithms or strategies

Complex logic with many conditions requires clear separation

Algorithms may change independently or need independent testing

Simple scenarios still suit if‑else

Logic is simple and stable

Performance‑critical situations where even a tiny overhead matters

Remember: choosing the right design pattern is more important than using a complex one for its own sake.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Design PatternsSoftware Architectureembedded systemsif-else
IT Services Circle
Written by

IT Services Circle

Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.