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(, "hello", , , "东北一家人"); per->speek(per);
OBJECT_CALL(per, speek); delete_person(&per); return ;
} // 具体的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, );
}
对于文中用到的原子锁, 参照 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(, 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(, 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 ;
}
上面是完整的构建冷饮厂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 ;
} // 开始添加注册代码, 实战中 订阅消息体 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 > ){
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) == )
++pool->curr;
//添加开启线程的信息
_thread_add(pool, tid);
pthread_mutex_unlock(mtx);
}
}
但是什么时候开始执行我们不知道. 将命令发布和命令的执行区分开来. 具体可以参看 C 实现有追求的线程池 探究 http://www.cnblogs.com/life2refuel/p/5322567.html
后记
到这里C相关设计模式基本就讲解完毕了, 其实C中设计模式将的极少, 最多的还是面向过程(切片). 强调结构和过程!
设计模式是开发中总结出来的可以复用的套路. 重要的是在于思想. 这里就用C简单模拟了一下. 错误是难免的, 期待更有意思.
C基础 常用设计模式粗解的更多相关文章
- PHP常用设计模式,PHP常用设计模式详解,PHP详解设计模式,PHP设计模式
PHP常用设计模式详解 单例模式: php交流群:159789818 特性:单例类只能有一个实例 类内__construct构造函数私有化,防止new实例 类内__clone私有化,防止复制对象 设置 ...
- php 常用设计模式详解
1.单例模式 构造函数必须为private 一个保存类实例静态成员变量 拥有一个访问这个实例的公共静态方法(常用getInstance()方法进行实例化单例类,通过instanceof操作符可以检测到 ...
- Java常用设计模式详解1--单例模式
单例模式:指一个类有且仅有一个实例 由于单例模式只允许有一个实例,所以单例类就不可通过new来创建,而所有对象都默认有一个无参的构造函数可以创建对象,所以单例类不仅不能提供public的构造方法,还需 ...
- GOF提出的23种设计模式是哪些 设计模式有创建形、行为形、结构形三种类别 常用的Javascript中常用设计模式的其中17种 详解设计模式六大原则
20151218mark 延伸扩展: -设计模式在很多语言PHP.JAVA.C#.C++.JS等都有各自的使用,但原理是相同的,比如JS常用的Javascript设计模式 -详解设计模式六大原则 设计 ...
- Javascript常用的设计模式详解
Javascript常用的设计模式详解 阅读目录 一:理解工厂模式 二:理解单体模式 三:理解模块模式 四:理解代理模式 五:理解职责链模式 六:命令模式的理解: 七:模板方法模式 八:理解javas ...
- hbase shell基础和常用命令详解(转)
HBase shell的基本用法 hbase提供了一个shell的终端给用户交互.使用命令hbase shell进入命令界面.通过执行 help可以看到命令的帮助信息. 以网上的一个学生成绩表的例子来 ...
- hbase shell基础和常用命令详解
HBase是Google Bigtable的开源实现,它利用Hadoop HDFS作为其文件存储系统,利用Hadoop MapReduce来处理HBase中的海量数据,利用Zookeeper作为协同服 ...
- 16个PHP设计模式详解
说明:该教程全部截选自实验楼教程[16个PHP设计模式详解]:主要介绍16个常用的设计模式的基础概念和技术要点,通过UML类图帮助理解设计模式中各个类之间的关联关系,针对每种设计模式都使用PHP完成了 ...
- [ 转载 ] Java开发中的23种设计模式详解(转)
Java开发中的23种设计模式详解(转) 设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类 ...
随机推荐
- NOI1997
T1 竞赛排名 分析:模拟 超级大模拟,太弱了,写个模拟都要2个小时..写的又难看又麻烦..还需努力 var n,i,j,k:longint; t1:real; x,y:..,..] of real; ...
- 具体数学二项式至生成函数章-----致敬Kunth
关于标题取得这么奇怪.因为在具体数学中.这两章是分开叙述的.并且分别叙述得淋漓尽致! 我只参悟其中关于生成函数的一小部分内容(暂时于我够用了.) 提二项式系数之前不得不提组合数.以往在高中用的是符号C ...
- [CF1083B]The Fair Nut and Strings
题目大意:在给定的长度为$n(n\leqslant5\times10^5)$的字符串$A$和字符串$B$中找到最多$k$个字符串,使得这$k$个字符串不同的前缀字符串的数量最多(只包含字符$a$和$b ...
- BZOJ1087:[SCOI2005]互不侵犯——题解
http://www.lydsy.com/JudgeOnline/problem.php?id=1087 Description 在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案.国王 ...
- 51NOD 1149:Pi的递推式——题解
http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1149 F(x) = 1 (0 <= x < 4) F(x) ...
- 2-SAT入门
大概学了一下2-SAT,写了一道模板和一道USACO 输出一个方案的话,tarjan缩点后倒着拓扑,染色输出. 求任何解下选哪个就得枚举每个点dfs来判断选哪个. HIT 1917(2-sat模板) ...
- IE6“无法打开站点,已终止操作”提示的解决
今天遇到一个问题,网站在IE 6下面打开会提示:Internet Explorer无法打开站点XXX.已终止操作. 先介绍一下网上常见的解决方法. 因为在页面还没有ready的时候就调用了htmlOb ...
- java中new一个对象放在循环体里面与外面的区别
首先说下问题: 这次在做项目的是出现了一个new对象在循环里面与外面造成的不同影响. 大家可以看到这个new的对象放在不同的位置产生的效果是不一样的. 经过多方查询与验证可以得出结论: * EasyU ...
- Moodle插件开发系列——XMLDB编辑器
Moodle插件开发系列——XMLDB编辑器 位置:网站管理>开发> XML编辑器 l XML编辑器是制作install.xml文件的工具,而install.xml是指定Moodle建立 ...
- redis 设置外网可访问
前提是你已经把redis的端口放到了防火墙计划中, /sbin/iptables -I INPUT -p tcp --dport 6379 -j ACCEPT /etc/rc.d/init.d/ipt ...