现在的位置: 首页 > 综合 > 正文

C语言-设计模式

2012年11月15日 ⁄ 综合 ⁄ 共 11595字 ⁄ 字号 评论关闭
文章目录

 设计模式的书相信很多人都看过。对于设计模式这样一种方法,相信不同的人有不同的理解。

 

C语言和设计模式(开篇)

关于软件设计方面的书很多,比如《重构》,比如《设计模式》。至于软件开发方式,那就更多了,什么极限编程、精益方法、敏捷方法。随着时间的推移,很多的方法又会被重新提出来。

其实,就我个人看来,不管什么方法都离不开人。一个人写不出二叉树,你怎么让他写?敏捷吗?你写一行,我写一行。还是迭代?写三行,删掉两行,再写三行。项目的成功是偶然的,但是项目的失败却有很多原因,管理混乱、需求混乱、设计低劣、代码质量差、测试不到位等等。就软件企业而言,没有比优秀的文化和出色的企业人才更重要的了。

从软件设计层面来说,一般来说主要包括三个方面:

(1)软件的设计受众,是小孩子、老人、女性,还是专业人士等等;
(2)软件的基本设计原则,以人为本、模块分离、层次清晰、简约至上、适用为先、抽象基本业务等等;
(3)软件编写模式,比如装饰模式、责任链、单件模式等等。

从某种意义上说,设计思想构成了软件的主题。软件原则是我们在开发中的必须遵循的准绳。软件编写模式是开发过程中的重要经验总结。灵活运用设计模式,一方面利于我们编写高质量的代码,另一方面也方便我们对代码进行维护。毕竟对于广大的软件开发者来说,软件的维护时间要比软件编写的时间要多得多。编写过程中,难免要有新的需求,要和别的模块打交道,要对已有的代码进行复用,那么这时候设计模式就派上了用场。我们讨论的主题其实就是设计模式。

讲到设计模式,人们首先想到的语言就是c#或者是java,最不济也是c++,一般来说没有人会考虑到c语言。其实,我认为设计模式就是一种基本思想,过度美化或者神化其实没有必要。其实阅读过linux kernel的朋友都知道,linux虽然自身支持很多的文件系统,但是linux自身很好地把这些系统的基本操作都抽象出来了,成为了基本的虚拟文件系统。

举个例子来说,现在让你写一个音乐播放器,但是要支持的文件格式很多,什么ogg,wav,mp3啊,统统要支持。这时候,你会怎么编写呢?如果用C++语言,你可能会这么写。

  1. class music_file
  2. {
  3. HANDLE hFile;
  4. public:
  5. void music_file() {}
  6. virtual ~music_file() {}
  7. virtual void read_file() {}
  8. virtual void play() {}
  9. virtual void stop() {}
  10. virtual void back() {}
  11. virtual void front() {}
  12. virtual void up() {}
  13. virtual void down() {}
  14. };
其实,你想想看,如果用C语言能够完成相同的抽象操作,那不是效果一样的吗?

  1. typedef struct _music_file
  2. {
  3. HANDLE hFile;
  4. void (*read_file)(struct _music_file* pMusicFile);
  5. void (*play)(struct _music_file* pMusicFile);
  6. void (*stop)(struct _music_file* pMusicFile);
  7. void (*back)(struct _music_file* pMusicFile);
  8. void (*front)(struct _music_file* pMusicFile);
  9. void (*down)(struct _music_file* pMusicFile);
  10. void (*up)(struct _music_file* pMusicFile);
  11. }music_file;
当然,上面的例子比较简单,但是也能说明一些问题。写这篇文章的目的一是希望和朋友们共同学习模式的相关内容,另一方面也希望朋友们能够活学活用,既不要迷信权威,也不要妄自菲薄。只要付出努力,付出汗水,肯定会有收获的。有些大环境你改变不了,那就从改变自己开始。万丈高楼平地起,一步一个脚印才能真真实实学到东西。如果盲目崇拜,言必google、微软、apple,那么除了带来几个唾沫星,还能有什么受用呢?无非白费了口舌而已。

希望和大家共勉。

C语言和设计模式(单件模式)

有过面试经验的朋友,或者对设计模式有点熟悉的朋友,都会对单件模式不陌生。对很多面试官而言,单件模式更是他们面试的保留项目。其实,我倒认为,单件模式算不上什么设计模式。最多也就是个技巧。

单件模式要是用C++写,一般这么写。

  1. #include <string.h>
  2. #include <assert.h>
  3. class object
  4. {
  5. public:
  6. static class object* pObject;
  7. static object* create_new_object()
  8. {
  9. if(NULL != pObject)
  10. return pObject;
  11. pObject = new object();
  12. assert(NULL != pObject);
  13. return pObject;
  14. }
  15. private:
  16. object() {}
  17. ~object() {}
  18. };
  19. class object* object::pObject = NULL;

单件模式的技巧就在于类的构造函数是一个私有的函数。但是类的构造函数又是必须创建的?怎么办呢?那就只有动用static函数了。我们看到static里面调用了构造函数,就是这么简单。

  1. int main(int argc,char* argv[])
  2. {
  3. object* pGlobal = object::create_new_object();
  4. return 1;
  5. }

 上面说了C++语言的编写方法,那C语言怎么写?其实也简单。大家也可以试一试。

  1. typedef struct _DATA
  2. {
  3. void* pData;
  4. }DATA;
  5. void* get_data()
  6. {
  7. static DATA* pData = NULL;
  8. if(NULL != pData)
  9. return pData;
  10. pData = (DATA*)malloc(sizeof(DATA));
  11. assert(NULL != pData);
  12. return (void*)pData;
  13. }

C语言和设计模式(之原型模式)

 

原型模式本质上说就是对当前数据进行复制。就像变戏法一样,一个鸽子变成了两个鸽子,两个鸽子变成了三个鸽子,就这么一直变下去。在变的过程中,我们不需要考虑具体的数据类型。为什么呢?因为不同的数据有自己的复制类型,而且每个复制函数都是虚函数。

用C++怎么编写呢,那就是先写一个基类,再编写一个子类。就是这么简单。

  1. class data
  2. {
  3. public:
  4. data () {}
  5. virtual ~data() {}
  6. virtual class data* copy() = 0;
  7. };
  8. class data_A :
    public data
  9. {
  10. public:
  11. data_A() {}
  12. ~data_A() {}
  13. class data* copy()
  14. {
  15. return new data_A();
  16. }
  17. };
  18. class data_B : public data
  19. {
  20. public:
  21. data_B() {}
  22. ~data_B() {}
  23. class data* copy()
  24. {
  25. return new data_B();
  26. }
  27. };

那怎么使用呢?其实只要一个通用的调用接口就可以了。

  1. class data* clone(class data* pData)
  2. {
  3. return pData->copy();
  4. }

就这么简单的一个技巧,对C来说,当然也不是什么难事。

  1. typedef struct _DATA
  2. {
  3. struct _DATA* (*copy) (struct _DATA* pData);
  4. }DATA;

假设也有这么一个类型data_A,

  1. DATA data_A = {data_copy_A};

既然上面用到了这个函数,所以我们也要定义啊。

  1. struct _DATA* data_copy_A(struct _DATA* pData)
  2. {
  3. DATA* pResult = (DATA*)malloc(sizeof(DATA));
  4. assert(NULL != pResult);
  5. memmove(pResult, pData, sizeof(DATA));
  6. return pResult;
  7. };

使用上呢,当然也不含糊。

  1. struct _DATA* clone(struct _DATA* pData)
  2. {
  3. return pData->copy(pData);
  4. };

C语言和设计模式(之组合模式)

组合模式听说去很玄乎,其实也并不复杂。为什么?大家可以先想一下数据结构里面的二叉树是怎么回事。为什么就是这么一个简单的二叉树节点既可能是叶节点,也可能是父节点?

  1. typedef struct _NODE
  2. {
  3. void* pData;
  4. struct _NODE* left;
  5. struct _NODE* right;
  6. }NODE;

那什么时候是叶子节点,其实就是left、right为NULL的时候。那么如果它们不是NULL呢,那么很明显此时它们已经是父节点了。那么,我们的这个组合模式是怎么一个情况呢?

  1. typedef struct _Object
  2. {
  3. struct _Object** ppObject;
  4. int number;
  5. void (*operate)(struct _Object* pObject);
  6. }Object;

就是这么一个简单的数据结构,是怎么实现子节点和父节点的差别呢。比如说,现在我们需要对一个父节点的operate进行操作,此时的operate函数应该怎么操作呢?

  1. void operate_of_parent(struct _Object* pObject)
  2. {
  3. int index;
  4. assert(NULL != pObject);
  5. assert(NULL != pObject->ppObject && 0 != pObject->number);
  6. for(index = 0; index < pObject->number; index ++)
  7. {
  8. pObject->ppObject[index]->operate(pObject->ppObject[index]);
  9. }
  10. }

当然,有了parent的operate,也有child的operate。至于是什么操作,那就看自己是怎么操作的了。

  1. void operate_of_child(struct _Object* pObject)
  2. {
  3. assert(NULL != pObject);
  4. printf("child node!\n");
  5. }

父节点也好,子节点也罢,一切的一切都是最后的应用。其实,用户的调用也非常简单,就这么一个简单的函数。

  1. void process(struct Object* pObject)
  2. {
  3. assert(NULL != pObject);
  4. pObject->operate(pObject);
  5. }

C语言和设计模式(之模板模式)

模板对于学习C++的同学,其实并不陌生。函数有模板函数,类也有模板类。那么这个模板模式是个什么情况?我们可以思考一下,模板的本质是什么。比如说,现在我们需要编写一个简单的比较模板函数。

 

 模板函数提示我们,只要比较的逻辑是确定的,那么不管是什么数据类型,都会得到一个相应的结果。固然,这个比较的流程比较简单,即使没有采用模板函数也没有关系。但是,要是需要拆分的步骤很多,那么又该怎么办呢?如果相通了这个问题,那么也就明白了什么是template模式。

 

比方说,现在我们需要设计一个流程。这个流程有很多小的步骤完成。然而,其中每一个步骤的方法是多种多样的,我们可以很多选择。但是,所有步骤构成的逻辑是唯一的,那么我们该怎么办呢?其实也简单。那就是在基类中除了流程函数外,其他的步骤函数全部设置为virtual函数即可。

 

 basic的类说明了基本的流程process是唯一的,所以我们要做的就是对step1和step2进行改写。

 

 

 所以,按照我个人的理解,这里的template主要是一种流程上的统一,细节实现上的分离。明白了这个思想,那么用C语言来描述template模式就不是什么难事了。
 因为在C++中process函数是直接继承的,C语言下面没有这个机制。所以,对于每一个process来说,process函数都是唯一的,但是我们每一次操作的时候还是要去复制一遍函数指针。而step1和step2是不同的,所以各种方法可以用来灵活修改自己的处理逻辑,没有问题。

 

 

 

C语言和设计模式(工厂模式)

工厂模式是比较简单,也是比较好用的一种方式。根本上说,工厂模式的目的就根据不同的要求输出不同的产品。比如说吧,有一个生产鞋子的工厂,它能生产皮鞋,也能生产胶鞋。如果用代码设计,应该怎么做呢?

 

  1. typedef struct _Shoe
  2. {
  3. int type;
  4. void (*print_shoe)(struct _Shoe*);
  5. }Shoe;

 就像上面说的,现在有胶鞋,那也有皮鞋,我们该怎么做呢?

 

 

  1. void print_leather_shoe(struct _Shoe* pShoe)
  2. {
  3. assert(NULL != pShoe);
  4. printf("This is a leather show!\n");
  5. }
  6. void print_rubber_shoe(struct _Shoe* pShoe)
  7. {
  8. assert(NULL != pShoe);
  9. printf("This is a rubber shoe!\n");
  10. }

 所以,对于一个工厂来说,创建什么样的鞋子,就看我们输入的参数是什么?至于结果,那都是一样的。

 

 

  1. #define LEATHER_TYPE 0x01
  2. #define RUBBER_TYPE 0x02
  3. Shoe* manufacture_new_shoe(int type)
  4. {
  5. assert(LEATHER_TYPE == type || RUBBER_TYPE == type);
  6. Shoe* pShoe = (Shoe*)malloc(sizeof(Shoe));
  7. assert(NULL != pShoe);
  8. memset(pShoe, 0, sizeof(Shoe));
  9. if(LEATHER_TYPE == type)
  10. {
  11. pShoe->type == LEATHER_TYPE;
  12. pShoe->print_shoe = print_leather_shoe;
  13. }
  14. else
  15. {
  16. pShoe->type == RUBBER_TYPE;
  17. pShoe->print_shoe = print_rubber_shoe;
  18. }
  19. return pShoe;
  20. }

 

C语言和设计模式(责任链模式)

责任链模式是很实用的一种实际方法。举个例子来说,我们平常在公司里面难免不了报销流程。但是,我们知道公司里面每一级的领导的报批额度是不一样的。比如说,科长的额度是1000元,部长是10000元,总经理是10万元。

那么这个时候,我们应该怎么设计呢?其实可以这么理解。比如说,有人来找领导报销费用了,那么领导可以自己先看看自己能不能报。如果费用可以顺利报下来当然最好,可是万一报不下来呢?那就只能请示领导的领导了。

  1. typedef struct _Leader
  2. {
  3. struct _Leader* next;
  4. int account;
  5. int (*request)(strcut _Leader* pLeader,int num);
  6. }Leader;

 所以这个时候,我们首先需要设置额度和领导。

  1. void set_account(struct _Leader* pLeader,int account)
  2. {
  3. assert(NULL != pLeader);
  4. pLeader->account = account;
  5. return;
  6. }
  7. void set_next_leader(conststruct _Leader* pLeader,
    struct _Leader* next)
  8. {
  9. assert(NULL != pLeader && NULL != next);
  10. pLeader->next = next;
  11. return;
  12. }

 此时,如果有一个员工过来报销费用,那么应该怎么做呢?假设此时的Leader是经理,报销额度是10万元。所以此时,我们可以看看报销的费用是不是小于10万元?少于这个数就OK,反之就得上报自己的领导了。

  1. int request_for_manager(struct _Leader* pLeader,int num)
  2. {
  3. assert(NULL != pLeader && 0 != num);
  4. if(num < 100000)
  5. return 1;
  6. else if(pLeader->next)
  7. return pLeader->next->request(pLeader->next, num);
  8. else
  9. return 0;
  10. }

C语言和设计模式(抽象工厂模式)

前面我们写过的工厂模式实际上是对产品的抽象。对于不同的用户需求,我们可以给予不同的产品,而且这些产品的接口都是一致的。而抽象工厂呢?顾名思义,就是说我们的工厂是不一定的。怎么理解呢,举个例子。

假设有两个水果店都在卖水果,都卖苹果和葡萄。其中一个水果店买白苹果和白葡萄,另外一个水果店卖红苹果和红葡萄。所以说,对于水果店而言,尽管都在卖水果,但是两个店卖的品种不一样。

既然水果不一样,那我们先定义水果。

  1. typedef struct _Apple
  2. {
  3. void (*print_apple)();
  4. }Apple;
  5. typedef struct _Grape
  6. {
  7. void (*print_grape)();
  8. }Grape;

上面分别对苹果和葡萄进行了抽象,当然它们的具体函数也是不一样的。

  1. void print_white_apple()
  2. {
  3. printf("white apple!\n");
  4. }
  5. void print_red_apple()
  6. {
  7. printf("red apple!\n");
  8. }
  9. void print_white_grape()
  10. {
  11. printf("white grape!\n");
  12. }
  13. void print_red_grape()
  14. {
  15. printf("red grape!\n");
  16. }

完成了水果函数的定义。下面就该定义工厂了,和水果一样,我们也需要对工厂进行抽象处理。

  1. typedef struct _FruitShop
  2. {
  3. Apple* (*sell_apple)();
  4. Apple* (*sell_grape)();
  5. }FruitShop;

所以,对于卖白苹果、白葡萄的水果店就该这样设计了,红苹果、红葡萄的水果店亦是如此。

  1. Apple* sell_white_apple()
  2. {
  3. Apple* pApple = (Apple*) malloc(sizeof(Apple));
  4. assert(NULL != pApple);
  5. pApple->print_apple = print_white_apple;
  6. return pApple;
  7. }
  8. Grape* sell_white_grape()
  9. {
  10. Grape* pGrape = (Grape*) malloc(sizeof(Grape));
  11. assert(NULL != pGrape);
  12. pGrape->print_grape = print_white_grape;
  13. return pGrape;
  14. }

这样,基本的框架就算搭建完成的,以后创建工厂的时候,

  1. FruitShop* create_fruit_shop(int color)
  2. {
  3. FruitShop* pFruitShop = (FruitShop*) malloc(sizeof(FruitShop));
  4. assert(NULL != pFruitShop);
  5. if(WHITE == color)
  6. {
  7. pFruitShop->sell_apple = sell_white_apple;
  8. pFruitShop->sell_grape = sell_white_grape;
  9. }
  10. else
  11. {
  12. pFruitShop->sell_apple = sell_red_apple;
  13. pFruitShop->sell_grape = sell_red_grape;
  14. }
  15. return pFruitShop;
  16. }

C语言和设计模式(迭代器模式)

使用过C++的朋友大概对迭代器模式都不会太陌生。这主要是因为我们在编写代码的时候离不开迭代器,队列有迭代器,向量也有迭代器。那么,为什么要迭代器呢?这主要是为了提炼一种通用的数据访问方法。

比如说,现在有一个数据的容器,

 

  1. typedef struct _Container
  2. {
  3. int* pData;
  4. int size;
  5. int length;
  6. Interator* (*create_new_interator)(struct _Container* pContainer);
  7. int (*get_first)(struct _Container* pContainer);
  8. int (*get_last)(struct _Container* pContainer);
  9. }Container;

 我们看到,容器可以创建迭代器。那什么是迭代器呢?

 

 

  1. typedef struct _Interator
  2. {
  3. void* pVector;
  4. int index;
  5. int(* get_first)(struct _Interator* pInterator);
  6. int(* get_last)(struct _Interator* pInterator);
  7. }Interator;

 我们看到,容器有get_first,迭代器也有get_first,这中间有什么区别?

 

 

  1. int vector_get_first(struct _Container* pContainer)
  2. {
  3. assert(NULL != pContainer);
  4. return pContainer->pData[0];
  5. }
  6. int vector_get_last(struct _Container* pContainer)
  7. {
  8. assert(NULL != pContainer);
  9. return pContainer->pData[pContainer->size -1];
  10. }
  11. int vector_interator_get_first(struct _Interator* pInterator)
  12. {
  13. Container* pContainer;
  14. assert(NULL != pInterator && NULL != pInterator->pVector);
  15. pContainer = (struct _Container*) (pInterator->pVector);
  16. return pContainer ->get_first(pContainer);
  17. }
  18. int vector_interator_get_last(struct _Interator* pInterator)
  19. {
  20. Container* pContainer;
  21. assert(NULL != pInterator && NULL != pInterator->pVector);
  22. pContainer = (struct _Container*) (pInterator->pVector);
  23. return pContainer ->get_last(pContainer);
  24. }

 看到上面的代码之后,我们发现迭代器的操作实际上也是对容器的操作而已。

C语言和设计模式(外观模式)

外观模式是比较简单的模式。它的目的也是为了简单。什么意思呢?举个例子吧。以前,我们逛街的时候吃要到小吃一条街,购物要到购物一条街,看书、看电影要到文化一条街。那么有没有这样的地方,既可以吃喝玩乐,同时相互又靠得比较近呢。其实,这就是悠闲广场,遍布全国的万达广场就是干了这么一件事。

首先,我们原来是怎么做的。

  1. typedef struct _FoodSteet
  2. {
  3. void (*eat)();
  4. }FoodStreet;
  5. void eat()
  6. {
  7. printf("eat here!\n");
  8. }
  9. typedef struct _ShopStreet
  10. {
  11. void (*buy)();
  12. }ShopStreet;
  13. void buy()
  14. {
  15. printf("buy here!\n");
  16. }
  17. typedef struct _BookStreet
  18. {
  19. void (*read)();
  20. }BookStreet;
  21. void read()
  22. {
  23. printf("read here");
  24. }

 下面,我们就要在一个plaza里面完成所有的项目,怎么办呢?

  1. typedef struct _Plaza
  2. {
  3. FoodStreet* pFoodStreet;
  4. ShopStreet* pShopStreet;
  5. BookStreet* pBookStreet;
  6. void (*play)(struct _Plaza* pPlaza);
  7. }Plaza;
  8. void play(struct _Plaza* pPlaza)
  9. {
  10. assert(NULL != pPlaza);
  11. pPlaza->pFoodStreet->eat();
  12. pPlaza->pShopStreet->buy();
  13. pPlaza->pBookStreet->read();
  14. }

C语言和设计模式(代理模式)

代理模式是一种比较有意思的设计模式。它的基本思路也不复杂。举个例子来说,以前在学校上网的时候,并不是每一台pc都有上网的权限的。比如说,现在有pc1、pc2、pc3,但是只有pc1有上网权限,但是pc2、pc3也想上网,此时应该怎么办呢?

此时,我们需要做的就是在pc1上开启代理软件,同时把pc2、pc3的IE代理指向pc1即可。这个时候,如果pc2或者pc3想上网,那么报文会先指向pc1,然后pc1把Internet传回的报文再发给pc2或者pc3。这样一个代理的过程就完成了整个的上网过程。

在说明完整的过程之后,我们可以考虑一下软件应该怎么编写呢?

  1. typedef struct _PC_Client
  2. {
  3. void (*request)();
  4. }PC_Client;
  5. void ftp_request()
  6. {
  7. printf("request from ftp!\n");
  8. }
  9. void http_request()
  10. {
  11. printf("request from http!\n");
  12. }
  13. void smtp_request()
  14. {
  15. printf("request from smtp!\n");
  16. }

这个时候,代理的操作应该怎么写呢?怎么处理来自各个协议的请求呢?

  1. typedef struct _Proxy
  2. {
  3. PC_Client* pClient;
  4. }Proxy;
  5. void process(Proxy* pProxy)
  6. {
  7. assert(NULL != pProxy);
  8. pProxy->pClient->request();
  9. }

C语言和设计模式(享元模式)

享元模式看上去有点玄乎,但是其实也没有那么复杂。我们还是用示例说话。比如说,大家在使用电脑的使用应该少不了使用WORD软件。使用WORD呢, 那就少不了设置模板。什么模板呢,比如说标题的模板,正文的模板等等。这些模板呢,又包括很多的内容。哪些方面呢,比如说字体、标号、字距、行距、大小等等。

  1. typedef struct _Font
  2. {
  3. int type;
  4. int

抱歉!评论已关闭.