C基础 常用设计模式粗解

引言

  面向对象, 设计模式是现代软件开发基石. C的面向过程已经很简洁, 但不代表C就没有面向对象.(libuv框架中C面向对象用的很多)

因为思想是互通的.全当熟悉一下那些常用的设计模式.先假定有一些语法和设计基础.本文会通过C实现下面内容.

  a.封装,继承,多态

  b.单例模式

  c.工厂模式

  d.抽象工厂模式

  e.观察者模式

  f.命令模式

(分析代码有点多和繁琐, 因为C去搭建, 都是从0到1, 能够复用的东西很少.) 主要在于回顾设计模式的思路.

先从a.封装,继承,多态开始抛砖引玉. 下面先说 封装

C面向对象,肯定从struct 上下功夫. 先展示一个 人的设计类

struct person;
typedef struct person * person_t;

#define _INT_NAME (64)

struct person {
    long id;
    char name[_INT_NAME];
    char sex;
    int age;
    char * address;
    
    // 说话方式
    void (* speek)(person_t this);
};

static void _speek_person(person_t this)
{
    printf("My name is %s, age %d old.\n", this->name, this->age);
}

// 具体的new函数
person_t new_person(long id, const char name[_INT_NAME], char sex, int age, const char * address);
// 具体的delete函数
void delete_person(person_t * pthis);

上面person_t就是我们构建的人的对象类, new_person 是构造函数, delete_person是析构函数. 对于C中对象类

中方法, 第一参数通用为对象指针. (记得lua实现面向对象也是这么实现的.)  第一个例子说详细些, 完整测试demo 如下

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct person;
typedef struct person * person_t;

#define _INT_NAME (64)

struct person {
    long id;
    char name[_INT_NAME];
    char sex;
    int age;
    char * address;
    
    // 说话方式
    void (* speek)(person_t this);
};

static void _speek_person(person_t this)
{
    printf("My name is %s, age %d old.\n", this->name, this->age);
}

// 具体的new函数
person_t new_person(long id, const char name[_INT_NAME], char sex, int age, const char * address);
// 具体的delete函数
void delete_person(person_t * pthis);

// 对象执行的方法
#define OBJECT_CALL(obj, call, ...) obj->##call(obj, ##__VA_ARGS__)

int main(int argc, char * argv[]) {
    
    person_t per = new_person(1, "hello", 0, 25, "东北一家人");
    
    per->speek(per);
    OBJECT_CALL(per, speek);

    delete_person(&per);

    return 0;
}


// 具体的new函数
struct person * 
new_person(long id, const char name[_INT_NAME], char sex, int age, const char * address)
{
    struct person * per = malloc(sizeof(struct person));
    if(NULL == per) {
        fprintf(stderr, "new_person malloc is error!\n");
        exit(EXIT_FAILURE);
    }
    per->id = id;
    strncpy(per->name, name, _INT_NAME);
    per->sex = sex;
    per->age = age;
    // strdup 不是标准规定接口, 推荐自己实现, 需要事后free
    per->address = strdup(address);
    per->speek = _speek_person;
    return per;
}

// 具体的delete函数
void 
delete_person(struct person ** pthis) {
    struct person * this;
    if((!pthis) || !(this = *pthis))
        return;
    
    // 释放内部变量
    if(this->address) {
        free(this->address);
        this->address = NULL;
    }

    // 释放本身用的变量
    free(this);
    *pthis = NULL;
}
View Code

那我们再说一下继承, 刚才是 person_t 人的类, 现在来了个男人类 需要继承人类, 可以构建为如下

struct man;
typedef struct man * man_t;

struct man {
    struct person person;
    double money;
};

// 具体的new函数
person_t new_man(long id, const char name[_INT_NAME], char sex, int age, const char * address, double money);
// 具体的delete函数
void delete_man(man_t * pthis);

对于多态, C实现也很容易. 请看下面demo演示 , 先定义一个人类接口说话的行为

// 人接口有多态行为
struct iperson {
    void (* speek)(void * this);
};

 

后面定义一个男人类

// 男人类
struct man;
typedef struct man * man_t;

struct man {
    struct iperson ipo;

    double money;
};

static void _speek_man(man_t this) {
    printf("man money = %lf\n", this->money);
}

man_t new_man(double money) {
    man_t this = malloc(sizeof(struct man));
    if(NULL == this) {
        fprintf(stderr, "new_man malloc is error!\n");
        exit(EXIT_FAILURE);
    }
    this->money = money;
    this->ipo.speek = _speek_man;
    return this;
}

void delete_man(man_t * pthis) {
    man_t this;
    if((!pthis) || !(this = *pthis))
        return;
    free(this);
    *pthis = NULL;
}

 

同样构建个女人类

// 女人类
struct woman;
typedef struct woman * woman_t;

struct woman {
    struct iperson ipo;

    double beauty;
};

static void _speek_woman(woman_t this) {
    printf("woman beauty = %lf\n", this->beauty);
}

woman_t new_woman(double beauty) {
    woman_t this = malloc(sizeof(struct woman));
    if(NULL == this) {
        fprintf(stderr, "new_man malloc is error!\n");
        exit(EXIT_FAILURE);
    }
    this->beauty = beauty;
    this->ipo.speek = _speek_woman;
    return this;
}

void delete_woman(woman_t * pthis)
{
    woman_t this;
    if((!pthis) || !(this = *pthis))
        return;
    free(this);
    *pthis = NULL;
}

对于多态行为处理采用统一接口

// 人接口有多态行为
struct iperson {
    void (* speek)(void * this);
};

// 多态行为处理
void speek_iperson(void * this) {
    struct iperson * iper = this;
    iper->speek(this);
}

这里采用的是运行时填充, 运行时确定那个行为被调用. 也用了C中万能类型 void *.  是不是感觉很有意思.

在我们使用上层语言的面向对象的时候, 也是需要this的, 但是这个this是隐式的, 编译器帮我们处理了, 多数放在寄存器中. 

编译器能够显示的找到它.  引言部分关于 [a.封装,继承,多态] 讲解就当这里了. 后面会逐个分析, 常用的设计模式. 感受软件设计的套路.

 

前言

b.单例模式

单例模式在C中用的异常多, 也当初设计的缺陷例如很多 *_r函数就是 对单例模式函数的补丁. (老的单例模式线程不安全, 加了线程安全版).

先举一个最简单的 单例, 可以说最简单最实用的单例就是 static. 单例是为了内存复用需求产生的.下面就是最简单的单例方式.

static int _getid(void) {
    static int _id;

    return ATOM_ADD_FETCH(_id, 1);
}

 

对于文中用到的原子锁, 参照 C基础 读写锁中级剖析 http://www.cnblogs.com/life2refuel/p/5634658.html

对于在堆上分配的单例对象 使用方法如下, 同样以上面 man_t 对象举例

// 单例对象, 在堆区分配
static man_t _signale_man;
man_t single_man(void) {
    // 加锁使用, 为了多线程安全
    static int _lock;

    if (!_signale_man) {
        SCATOM_LOCK(_lock);
        if (!_signale_man) {
            _signale_man = calloc(1, sizeof(struct man));
            if (!_signale_man) {
                fprintf(stderr, "single_man calloc is error!\n");
                exit(EXIT_FAILURE);
            }
            _signale_man->ipo.speek = _speek_man;
        }
        SCATOM_UNLOCK(_lock);
    }

    return _signale_man;
}

 

这个单例对象存在一次内存泄漏, 可以交给操作系统操作. 如果需要精细处理, 那就对 _signal_man 对象进行处理, 最后调用free函数试试. 扯一点,

有没有发现malloc , calloc, realloc c中调用很繁琐. 下次单独封装一个内存管理使用库. 单例模式就这些内容, 最完美的单例就是静态变量.

 c.工厂模式

 工厂模式在面向对象较大项目中用的场景很多, 事务工厂, 任务工厂, 成就工厂等. 核心思路是按照不同需求生成不同的对象(产品). 生产方法走统一的接口.

参照下面例子, 家庭会根据不同吃饭类型, 做饭. 是不是觉得工厂模式不过如此. 但是确实很实用.

// 工厂类型
enum emeal {
    Meal_Begin,                //开始位置
    Meal_Breakfast,            //早餐
    Meal_Lunch,                //晚餐
    Meal_Dinner,            //中餐
    Meal_Midnightsnack,        //宵夜
    Meal_End                //结束位置    
};

// 工厂生产的产品
struct family {
    enum emeal type;
    void (* eat)(struct family * fiy);
};

// 具体工厂生产方法
static void _meal_breakfast(struct family * fiy) {
    printf("beign eat breakfast, type = %d.\n", fiy->type);
}

static void _meal_midnightsnack(struct family * fiy) {
    printf("beign eat midnightsnack, type = %d.\n", fiy->type);
}

// 工厂开始按照需求生产
struct family * new_meal(enum emeal type)
{
    struct family * fly;

    if(type <= Meal_Begin || type >= Meal_End ) {
        fprintf(stderr, "new_meal type = %d is error!", type);
        exit(EXIT_FAILURE);
    }
    
    if((fly = calloc(1, sizeof(struct family))) == NULL) {
        fprintf(stderr, "new_meal calloc is error!", type);
        exit(EXIT_FAILURE);
    }
    fly->type = type;

    switch(type) {
    case Meal_Breakfast:            //早餐
        fly->eat = _meal_breakfast;
        break;
    case Meal_Lunch:                //晚餐
        break;
    case Meal_Dinner:                //中餐
        break;
    case Meal_Midnightsnack:        //宵夜    
        fly->eat = _meal_midnightsnack;
        break;
    }

    return fly;
};

扯一点C程序设计, C开发用枚举很少, 因为本质就是宏. 当你定义枚举的时候推荐第一个字符为'e', 后面采用头字母大写, 方便和宏区分开来.一看就知道这是枚举''宏''.

每一分提升都是捉摸滚打, 从错误,感觉不好中优化提升美的意识.

 

正文

d.抽象工厂模式

抽象工厂模式是对工厂模式的扩展. 工厂创建一种产品,抽象工厂创建的是一组产品.当你发现,有一个接口可以有多种实现的时候,可以考虑使用工厂方法来创建实例.
当你返现,有一组接口可以有多种实现方案的时候,可以考虑使用抽象工厂创建实例组。对工厂再包装一层, 我们举个例子如下.

#include <stdio.h>
#include <stdlib.h>

/*
 * 假定有两家冷饮制作厂, 都有制作冷饮和销售冷饮两个行为
 */

// 制作冷饮的接口
struct imakecooler {
    void (* make)();
};

// 销售冷饮的接口
struct isellcooler {
    void (* sell)();
};

// 冷饮抽象工厂接口
struct icooler {
    struct imakecooler * (* makecooler)();        // 得到制作冷饮接口
    struct isellcooler * (* sellcooler)();        // 得到销售冷饮接口
};

// 第一家冷饮店提供对应制作和销售接口实现
static void _make_one() {
    puts("第一家冷饮店制作冰淇淋.");
}

static void _sell_one() {
    puts("第一家冷饮店销售和超市合作.");
}

// 第二家冷饮店制作和销售接口实现
static void _make_two() {
    puts("第二家冷饮店制作老北京和大东北.");
}

static void _sell_two() {
    puts("第二家冷饮店销售是自营.");
}

// 第一家冷饮店制作和销售接口工厂实现
static struct imakecooler * _make_one_create() {
    static struct imakecooler imake = { _make_one };
    return &imake;
}

static struct isellcooler * _sell_one_create() {
    static struct isellcooler isell = { _sell_one };
    return &isell;
};

// 第二家冷饮店制作和销售接口工厂实现
static struct imakecooler * _make_two_create() {
    static struct imakecooler imake = { _make_two };
    return &imake;
}

static struct isellcooler * _sell_two_create() {
    static struct isellcooler isell = { _sell_two };
    return &isell;
};

// 具体抽象工厂创建
enum ecooler {
    Cooler_Begin,            // 开始断点
    Cooler_One,                // 第一家冷饮厂
    Cooler_Two,                // 第二家冷饮厂
    Cooler_End                // 结束断点
};

struct icooler * cooler_create(enum ecooler type) {
    struct icooler * icr;

    if(type <= Cooler_Begin || type >= Cooler_End) {
        fprintf(stderr, "cooler_create type = %d is error!", type);
        exit(EXIT_FAILURE);
    }

    if((icr = malloc(sizeof(struct icooler))) == NULL) {
        fprintf(stderr, "cooler_create calloc is error!", type);
        exit(EXIT_FAILURE);
    }

    switch(type) {
    case Cooler_One:            //第一家冷饮工厂
        icr->makecooler = _make_one_create;
        icr->sellcooler = _sell_one_create;
        break;
    case Cooler_Two:            //第二家冷饮工厂
        icr->makecooler = _make_two_create;
        icr->sellcooler = _sell_two_create;
        break;
    }

    return icr;
}

/*
 * 这里分享抽象工厂例子, 创建使用和销毁
 * 
 */
int main(int argc, char * argv[]) {
    // 创建抽象工厂并测试
    struct icooler * icr = cooler_create(Cooler_Two);
    icr->makecooler()->make();
    free(icr);
    return 0;
}

上面是完整的构建冷饮厂one和two, 并给出真实跑的例子, 还是很有意思的. 喜欢将抽象工厂模式理解为工厂模式的再包装一层. 多个生产工厂.

 

e.观察者模式

对于观察者模式,有时候也叫订阅模式. 等同于你定了小区酸奶,每天都会给你送来.观察者模式开发中还是很常见的, 消息发送, 消息同步.等.

一般是实现包括, 订阅者, 订阅某个消息. 发布者, 发布消息之后订阅者就能收到通知. 看下面完整验证demo. 本质是

订阅者 -> 订阅信息放入 订阅链表中

发布者 -> 发布消息, 订阅链表循环一遍

#include <stdio.h>
#include <stdlib.h>

// 注册消息体
typedef void (* subscribe_f)(const char * str);

// 观察者(订阅者)消息链
struct observer {
    int id;                        // 唯一观察者id
    subscribe_f subscribe;        // 消息过来,观察者注册的消息回调

    struct observer * next;        // 订阅消息链, 下一个节点
};

// 开始添加注册代码, 实战中 订阅消息体 head 对于观察者是不可见的
int observer_add(struct observer ** phead, subscribe_f subscribe);
// 发布者发布消息
void observer_update(struct observer * head, const char * str);
// 观察者链销毁
void observer_delete(struct observer * head);

static void _subsleep(const char * str) {
    printf("等你都等睡着了 -> [%s]\n", str);
}

static void _subgame(const char * str) {
    printf("打游戏又来烦我 -> [%s]\n", str);
}

/*
 * 观察者模式, 处理
 */
int main(int argc, char * argv[]) {
    
    struct observer * head = NULL;
    
    // 开始订阅
    observer_add(&head, _subsleep);
    observer_add(&head, _subgame);

    // 发布者发布消息
    observer_update(head, "苍老师");

    // 释放内存
    observer_delete(head);
    
    getchar();
    return 0;
}

// 开始添加注册代码, 实战中 订阅消息体 head 对于观察者是不可见的
int 
observer_add(struct observer ** phead, subscribe_f subscribe) {
    static int _id;
    struct observer * node = malloc(sizeof(struct observer));
    if(NULL == node) {
        fprintf(stderr, "observer_add malloc is error!");
        exit(EXIT_FAILURE);
    }
    node->id = ++_id;
    node->subscribe = subscribe;
    node->next = *phead;

    // 重新构建头指针, 尾查法
    *phead = node;
    return _id;
}

// 发布者发布消息
void 
observer_update(struct observer * head, const char * str) {
    while(head) {
        head->subscribe(str);
        head = head->next;
    }
}

// 观察者链销毁
void 
observer_delete(struct observer * head) {
    while(head) {
        struct observer * next = head->next;;
        free(head);
        head = next;
    }
}

 

f.命令模式

  命令模式在C开发很少见, 在其它语言开发中碰到过几次, 例如在任务系统中, 不同命令功能封装成一个类.命令模式的主要职责是把命令的发布者和

命令的执行者分离开. 举个例子, 公司董事长想做个新项目, 通知了每个leader, leader知道意思了, 肯定不是自己做, 那就底下的大头兵开始搞.

这就是发布命令和执行命令分开. 在C中举个简单例子 , 线程池中注册执行线程(发布命令)

/*
 * 在当前线程池中添加待处理的线程对象.
 * pool        : 线程池对象, sp_new 创建的那个
 * run        : 运行的函数体, 返回值void, 参数void*
 * arg        : 传入运行的参数
 *            : 不需要返回值
 */
void 
sp_add(threadpool_t pool, vdel_f run, void* arg)
{
    struct threadjob* job = _new_threadjob(run, arg);
    pthread_mutex_t* mtx = &pool->mutex;
    
    pthread_mutex_lock(mtx);
    if(!pool->head) //线程池中没有线程头,那就设置线程头
        pool->head = job;
    else
        pool->tail->next = job;
    pool->tail = job;
    
    // 有空闲线程,添加到处理任务队列中,直接返回
    if(pool->idle > 0){
        pthread_mutex_unlock(mtx);
        // 这是一种算法, 先释放锁后发送信号激活线程,速度快,缺点丧失线程执行优先级
        pthread_cond_signal(&pool->threads->cond);
    }
    else if(pool->curr < pool->size){ // 没有那就新建线程, 条件不满足那就等待
        pthread_t tid;
        if(pthread_create(&tid, NULL, (void* (*)(void*))_consumer, pool) == 0)
            ++pool->curr;
        //添加开启线程的信息
        _thread_add(pool, tid);
        pthread_mutex_unlock(mtx);
    }
}

但是什么时候开始执行我们不知道. 将命令发布和命令的执行区分开来.  具体可以参看 C 实现有追求的线程池 探究 http://www.cnblogs.com/life2refuel/p/5322567.html

 

后记

  到这里C相关设计模式基本就讲解完毕了, 其实C中设计模式将的极少, 最多的还是面向过程(切片). 强调结构和过程!

设计模式是开发中总结出来的可以复用的套路. 重要的是在于思想. 这里就用C简单模拟了一下. 错误是难免的, 期待更有意思.

 

posted on 2016-07-19 15:29  喜ω欢  阅读(1078)  评论(1编辑  收藏  举报