Fundamentals 13 min read

Mastering OOP in C: Implement the State Pattern with Real Code

This article walks you through applying object‑oriented concepts in C by implementing the State design pattern, covering class simulation with structs, inheritance via macros, lifecycle management, polymorphism, and a complete client example that demonstrates state transitions for a water model.

21CTO
21CTO
21CTO
Mastering OOP in C: Implement the State Pattern with Real Code

State Pattern Review

The State pattern wraps an object's behavior in distinct state objects, each derived from a common abstract state class; when the object's internal state changes, its behavior changes as if its class had changed.

Typical usage scenarios:

An object's behavior depends on its state and must change at runtime.

The code contains many conditionals related to object state.

In this pattern the context holds a collection of state objects and delegates state‑related behavior to them.

C Language Practice

Water can exist in solid, liquid, and gaseous states, transitioning between them based on temperature.

State diagram
State diagram

Temperature rules:

Below 0 °C → solid.

0 °C ≤ temperature < 100 °C → liquid.

≥ 100 °C → gaseous.

Class Design

Water : environment class that holds a collection of state objects.

State : abstract class declaring common behavior for all states.

SolidState , LiquidState , GaseousState : concrete subclasses representing each water state.

Class Definition

In C, structs simulate classes; function pointers emulate methods, and all members are public.

// Water.h
struct State;
typedef struct Water {
    int (*getTemperature)(struct Water* self);
    void (*riseTemperature)(struct Water* self, int step);
    void (*reduceTemperature)(struct Water* self, int step);
    void (*behavior)(struct Water* self);
    void (*changeState)(struct Water* self);
    struct State* states[MAX_STATE_NUM];
    struct State* currentState;
    int temperature;
} Water;

Class Inheritance

Concrete state structs inherit the abstract State struct by placing it as the first member, allowing simple casting to achieve compile‑time polymorphism.

// State.h
struct Water;
typedef struct State {
    VIRTUAL(void (*handle)(struct State* self, struct Water* water));
    VIRTUAL(Boolean (*match)(struct State* self, int temperature));
    const char* (*getName)(struct State* self);
    char* name;
} State;

typedef struct SolidState { State base; } SolidState;
typedef struct LiquidState { State base; } LiquidState;
typedef struct GaseousState { State base; } GaseousState;

Object Lifecycle Management

Object Creation

A newWater function mimics C++'s new operator: it allocates memory, calls an initializer, and sets up state objects.

// Water.c
Water* newWater(int temperature) {
    Water* water = (Water*)malloc(sizeof(Water));
    if (water != NULL) {
        ErrCode err = waterInit(water, temperature);
        if (err != ERR_SUCC) {
            return NULL;
        }
    }
    return water;
}

ErrCode waterInit(Water* self, int temperature) {
    self->states[0] = newState(SOLID);
    self->states[1] = newState(LIQUID);
    self->states[2] = newState(GASEOUS);
    // initialize other members …
    self->temperature = temperature;
    waterChangeState(self);
    // assign method pointers …
    return ERR_SUCC;
}

Object Destruction

Corresponding deleteWater and waterCleanUp functions release resources and free memory.

void deleteWater(Water* water) {
    waterCleanUp(water);
    free(water);
}

void waterCleanUp(Water* self) {
    for (int i = 0; i < MAX_STATE_NUM; i++) {
        deleteState(self->states[i]);
        self->states[i] = NULL;
    }
    self->currentState = NULL;
}

Object Polymorphism

The Water struct holds an array of State* pointers; during initialization each pointer is set to a concrete state object via the factory‑style newState function. The first matching state becomes the current state, enabling runtime behavior changes through simple casts.

Client Invocation

The client creates a Water object at 25 °C (liquid), raises and lowers the temperature to trigger state transitions, and finally destroys the object.

// Client.c
void statePatternRun() {
    Water* water = newWater(25);
    if (water == NULL) return;
    water->behavior(water);
    water->riseTemperature(water, 50);
    water->behavior(water);
    water->reduceTemperature(water, 100);
    water->behavior(water);
    water->riseTemperature(water, 200);
    water->behavior(water);
    deleteWater(water);
}

Running the program produces logs showing the water object's personality changing with its state.

Conclusion

The tutorial demonstrates how to bring object‑oriented ideas into C by defining classes with structs, simulating inheritance with macros, managing object lifecycles, and achieving polymorphism through pointer casting. These techniques enable clean, maintainable designs even in a language that lacks native OOP support.

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 PatternsMemory ManagementCState Patternobject‑oriented programmingPolymorphism
21CTO
Written by

21CTO

21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service 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.