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; }
那我们再说一下继承, 刚才是 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简单模拟了一下. 错误是难免的, 期待更有意思.