引言

  面向对象, 设计模式是现代软件开发基石. 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基础 常用设计模式粗解的更多相关文章

  1. PHP常用设计模式,PHP常用设计模式详解,PHP详解设计模式,PHP设计模式

    PHP常用设计模式详解 单例模式: php交流群:159789818 特性:单例类只能有一个实例 类内__construct构造函数私有化,防止new实例 类内__clone私有化,防止复制对象 设置 ...

  2. php 常用设计模式详解

    1.单例模式 构造函数必须为private 一个保存类实例静态成员变量 拥有一个访问这个实例的公共静态方法(常用getInstance()方法进行实例化单例类,通过instanceof操作符可以检测到 ...

  3. Java常用设计模式详解1--单例模式

    单例模式:指一个类有且仅有一个实例 由于单例模式只允许有一个实例,所以单例类就不可通过new来创建,而所有对象都默认有一个无参的构造函数可以创建对象,所以单例类不仅不能提供public的构造方法,还需 ...

  4. GOF提出的23种设计模式是哪些 设计模式有创建形、行为形、结构形三种类别 常用的Javascript中常用设计模式的其中17种 详解设计模式六大原则

    20151218mark 延伸扩展: -设计模式在很多语言PHP.JAVA.C#.C++.JS等都有各自的使用,但原理是相同的,比如JS常用的Javascript设计模式 -详解设计模式六大原则 设计 ...

  5. Javascript常用的设计模式详解

    Javascript常用的设计模式详解 阅读目录 一:理解工厂模式 二:理解单体模式 三:理解模块模式 四:理解代理模式 五:理解职责链模式 六:命令模式的理解: 七:模板方法模式 八:理解javas ...

  6. hbase shell基础和常用命令详解(转)

    HBase shell的基本用法 hbase提供了一个shell的终端给用户交互.使用命令hbase shell进入命令界面.通过执行 help可以看到命令的帮助信息. 以网上的一个学生成绩表的例子来 ...

  7. hbase shell基础和常用命令详解

    HBase是Google Bigtable的开源实现,它利用Hadoop HDFS作为其文件存储系统,利用Hadoop MapReduce来处理HBase中的海量数据,利用Zookeeper作为协同服 ...

  8. 16个PHP设计模式详解

    说明:该教程全部截选自实验楼教程[16个PHP设计模式详解]:主要介绍16个常用的设计模式的基础概念和技术要点,通过UML类图帮助理解设计模式中各个类之间的关联关系,针对每种设计模式都使用PHP完成了 ...

  9. [ 转载 ] Java开发中的23种设计模式详解(转)

    Java开发中的23种设计模式详解(转)   设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类 ...

随机推荐

  1. 【bzoj1858】[Scoi2010]序列操作 线段树区间合并

    题目描述 lxhgww最近收到了一个01序列,序列里面包含了n个数,这些数要么是0,要么是1,现在对于这个序列有五种变换操作和询问操作: 0 a b 把[a, b]区间内的所有数全变成0 1 a b ...

  2. html的body内标签之图片及表格

    <li> list 标签定义和用法: <li> 标签定义列表项目. <li> 标签可用在有序列表 (<ol>) 和无序列表 (<ul>) 中 ...

  3. [CQOI2014]数三角形 组合数 + 容斥 + gcd

    推导过程 : 组合数+容斥原理+gcd 正确做法是暴力的一种优化,ans=所有情况 - 平行坐标轴的三点共线 - 斜线三点共线 如果快速求斜线三点共线: 首先要知道一个结论,对于点(a,b) (x,y ...

  4. 自学Python快速入门

    1 helloworld#基本语法print("hello") #换行print('1221312\12312312\2312312321312\21312312') ##表示注释 ...

  5. 第13届 广东工业大学ACM程序设计大赛 C题 平分游戏

    第13届 广东工业大学ACM程序设计大赛 C题 平分游戏 题目描述 转眼间又过了一年,又有一届的师兄师姐要毕业了. ​ 有些师兄师姐就去了景驰科技实习. 在景驰,员工是他们最宝贵的财富.只有把每一个人 ...

  6. Leetcode中字符串总结

    本文是个人对LeetCode中字符串类型题目的总结,纯属个人感悟,若有不妥的地方,欢迎指出. 一.有关数字 1.数转换 题Interger to roman和Roman to integer这两题是罗 ...

  7. CodeForces.5A Chat Server's Outgoing Traffic

    Chat Server's Outgoing Traffic 点我挑战提目 考察点 模拟 字符串 Time Mem Len Lang 30 0 543 c++ 题意分析 给出类似一个群的即时通讯系统, ...

  8. 解决jsp两种提交方式乱码 的方法

    解决中文乱码 ---post提交方式  需要在处理页面添加request.setCharacterEncoding("utf-8"); 制定请求的编码,调用一下request.ge ...

  9. 使用httpclient post请求中文乱码解决办法

    使用httpclient post请求中文乱码解决办法   在使用httpclient发送post请求的时候,接收端中文乱码问题解决. 正文: 我们都知道,一般情况下使用post请求是不会出现中文乱码 ...

  10. crontab使用进程锁解决冲突

    想到一个问题,如果在crontab里有个定时任务设置为一分钟执行一次,但是它执行的时间可能会超过一分钟,此时crontab一分钟后会再次运行该脚本吗?这样会不会出现冲突呢?网上找了下,说可以用Linu ...