C 语言的面向对象之旅

2019-09-30  本文已影响0人  _张晓龙_

引言

C 语言诞生于 1972 年,已经走过 47 个年头了,可以说算是一门很陈旧的语言了,但现在仍然很流行,而且持续位于编程语言排行榜的前列,生命力非常旺盛。

C 语言经常被贴上 面向过程 的标签,很多同学都没想过或实践过用面向对象的思想来开发 C 语言的代码。当你的 C 系统比较简单时,用面向过程还是面向对象的思想来开发都可以,而当你的 C 系统比较庞大和复杂时,用面向对象的思想来开发则会显著提升系统的可理解性、可维护性和可扩展性。

本文以读者耳熟能详的一个设计模式(状态模式)为例,带领大家共同体验一段 C 语言的面向对象之旅,使得大家能快速掌握 C 语言的面向对象开发套路。

状态模式回顾

state-pattern.png

状态模式把对象的行为包装在不同的状态对象里,每一个状态对象都有一个共同的抽象状态类。当一个对象的内在状态改变时允许改变其行为,这个对象看起来是改变了其类。

使用场景:

上下文和状态处理对象:

C 语言实战

水有固态、液态和气态三种状态,而且这三种状态可以互相转化,如下图所示:

water.png

水有温度,我们可以认为:

类的设计

类图和设计模式中完全一样,不再赘述。

类的定义

在 C 语言中只能用结构体 struct 来模拟类,而类有属性和方法:

Water 类定义,代码示例:

//  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;

代码说明:

类的继承

具体子类继承抽象类的方法和属性,即具体子类声明的方法和属性以抽象类的声明为基础,然后根据实际需要再扩充。考虑将抽象类的变量作为具体子类的第一个成员,这样抽象类指针和具体子类指针都可以指向具体子类对象的地址。

抽象类代码示例:

// State.h

struct Water;

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

代码说明:

具体子类 SolidState、LiquidState 和 GaseousState 都继承抽象类 State,代码示例:

// State.h

typedef struct SolidState
{
    State base;
}SolidState;

typedef struct LiquidState
{
    State base;
}LiquidState;

typedef struct GaseousState
{
    State base;
}GaseousState;

代码说明:

对象的生命周期管理

面向对象开发的一个核心思想就是对象,即把任何可以类型化的东西看成对象,而把程序之间的交互和调用以对象之间传递消息的形式来实现。对象如何创建及销毁,是对象的生命周期管理要关注的主要问题。

对象创建

我们通过 create 创建函数来模拟 C++ 中的 new 运算符:先申请对象内存,然后调用构造函数完成对象初始化。

对于 Water 对象,独立封装了创建函数 waterCreate 和构造函数 waterInit:

// Water.c

Water* waterCreate(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] = stateCreate(SOLID);
    self->states[1] = stateCreate(LIQUID);
    self->states[2] = stateCreate(GASEOUS);
    for (int i = 0; i < MAX_STATE_NUM; i++)
    {
        if (self->states[i] == NULL)
        {
            return ERR_MEM_MALLOC_FAILED;
        }
    }
    self->temperature = temperature;
    waterChangeState(self);
    self->getTemperature = waterGetTemperature;
    self->riseTemperature = waterRiseTemperature;
    self->reduceTemperature = waterReduceTemperature;
    self->changeState = waterChangeState;
    self->behavior = waterBehavior;
    return ERR_SUCC;
}

代码说明:

对于 State 对象,统一封装了创建函数 stateCreate:

State* stateCreate(StateIdentifier identifier)
{
    switch(identifier)
    {
    case SOLID: return solidStateCreate();
    case LIQUID: return liquidStateCreate();
    case GASEOUS: return gaseousStateCreate();
    default: return NULL;
    }
}

void stateInit(State* self,
        void (*handle)(struct State* self, struct Water* water),
        Boolean (*match)(struct State* self, int temperature),
        char* name)
{
    self->handle = handle;
    self->match = match;
    self->getName = stateGetName;
    self->name = name;
}

State* solidStateCreate()
{
    State* state = (State*)malloc(sizeof(SolidState));
    if (state != NULL)
    {
       solidStateInit((SolidState*)state);
    }
    return state;
}

void solidStateInit(SolidState* self)
{
    stateInit((State*)self, solidStateHandle, solidStateMatch, "SolidState");
}

代码说明:

对象销毁

我们通过 destroy 销毁函数来模拟 C++ 中的 delete 运算符:先调用析构函数释放对象持有的资源,然后释放对象的内存。

对于 Water 对象,独立封装了销毁函数 waterDestroy 和析构函数 waterCleanUp:

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

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

代码说明:

对于 State 对象,统一封装了销毁函数 stateDestroy:

void stateDestroy(State* state)
{
    free(state);
}

代码说明:

对象的多态

环境类 Water 持有抽象类 State 的指针:

// Water.h

struct State* states[MAX_STATE_NUM];
struct State* currentState;

在 Water 的构造函数中对这些 State 指针变量通过 State 的创建函数进行初始化:

self->states[0] = stateCreate(SOLID);
self->states[1] = stateCreate(LIQUID);
self->states[2] = stateCreate(GASEOUS);

for (int i = 0; i < MAX_STATE_NUM; i++)
{
    if (self->states[i]->match(self->states[i], self->temperature))
    {
        self->currentState = self->states[i];
        return;
    }
}

可以看出,抽象类指针指向了具体子类对象的地址。通过将基类的变量作为子类的第一个成员来模拟继承,仅仅通过类型强转就能轻松实现多态。就这么简单?对,就这么简单,而且这种多态本质上是一种编译时多态。

客户端调用

客户端调用的过程:

客户端代码的示例:

// Client.c

void statePatternRun()
{
    Water* water = waterCreate(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);

    waterDestroy(water);
}

运行程序,日志如下:

$ ./design-pattern-in-c state-pattern
LiquidState: 我性格温和,当前体温25摄氏度,我可滋润万物,饮用我可让你活力倍增……
LiquidState: 我性格温和,当前体温75摄氏度,我可滋润万物,饮用我可让你活力倍增……
SolidState: 我性格高冷,当前体温-25摄氏度,我坚如钢铁,仿如一冷血动物,请用我砸人……
GaseousState: 我性格热烈,当前体温175摄氏度,飞向天空是我毕生的梦想,在这你将看不到我的存在,我将达到无我的境界……

小结

至此,一段 C 语言的面向对象之旅已到达终点。

在这段奇妙的旅行中,我们掌握了一些用 C 语言实践面向对象思想的方法和技巧:

案例代码链接:https://github.com/agiledragon/design-pattern-c

上一篇 下一篇

猜你喜欢

热点阅读