引言

  面向对象, 设计模式是现代软件开发基石. 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. Skills - CF613B

    Lesha plays the recently published new version of the legendary game hacknet. In this version charac ...

  2. POJ 3261 Milk Patterns (后缀数组,求可重叠的k次最长重复子串)

    Milk Patterns Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 16742   Accepted: 7390 Ca ...

  3. [ZJOI2011]最小割 & [CQOI2016]不同的最小割 分治求最小割

    题面: [ZJOI2011]最小割 [CQOI2016]不同的最小割 题解: 其实这两道是同一道题.... 最小割是用的dinic,不同的最小割是用的isap 其实都是分治求最小割 简单讲讲思路吧 就 ...

  4. 【SPOJ】Highways(矩阵树定理)

    [SPOJ]Highways(矩阵树定理) 题面 Vjudge 洛谷 题解 矩阵树定理模板题 无向图的矩阵树定理: 对于一条边\((u,v)\),给邻接矩阵上\(G[u][v],G[v][u]\)加一 ...

  5. BZOJ4199:[NOI2015]品酒大会——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=4199 https://www.luogu.org/problemnew/show/P2178#su ...

  6. BZOJ1057:[ZJOI2007]棋盘制作——题解

    http://www.lydsy.com/JudgeOnline/problem.php?id=1057 https://www.luogu.org/problemnew/show/P1169 国际象 ...

  7. bzoj4773: 负环(倍增floyd)

    浴谷夏令营例题...讲师讲的很清楚,没看题解代码就自己敲出来了 f[l][i][j]表示i到j走2^l条边的最短距离,显然有f[l][i][j]=min(f[l][i][j],f[l-1][i][k] ...

  8. SRM16 B-2(DP)

    老鼠和洞按坐标排序 f[i][j]表示前i个洞进j只老鼠的最短距离 比赛的时候强行分三类去推式子,推是推出来了,也看出来是可以用三个单调队列去优化的,但是太繁琐了,要我敲我真没办法T^T 赛后经 on ...

  9. 爬虫实例——通过JS控制滚动条

    案例 某位淘女郎的某个相册 有能力的童鞋可以先尝试一下爬取每张照片的链接. 我曾经尝试过几种方法,下面一一介绍: 第一种方法,采用requests和BeautifulSoup: import requ ...

  10. [HNOI2010] 弹飞绵羊 (分块)

    [HNOI2010] 弹飞绵羊 题目描述 某天,Lostmonkey发明了一种超级弹力装置,为了在他的绵羊朋友面前显摆,他邀请小绵羊一起玩个游戏.游戏一开始,Lostmonkey在地上沿着一条直线摆上 ...