设计模式的书相信很多人都看过。对于设计模式这样一种方法,相信不同的人有不同的理解。
关于软件设计方面的书很多,比如《 重构》,比如《 设计模式》。至于软件开发方式,那就更多了,什么极限编程、精益方法、敏捷方法。随着时间的推移,很多的方法又会被重新提出来。
其实,就我个人看来,不管什么方法都离不开人。一个人写不出二叉树,你怎么让他写?敏捷吗?你写一行,我写一行。还是迭代?写三行,删掉两行,再写三行。项目的成功是偶然的,但是项目的失败却有很多原因,管理混乱、需求混乱、设计低劣、代码质量差、测试不到位等等。就软件企业而言,没有比优秀的文化和出色的企业人才更重要的了。
从软件设计层面来说,一般来说主要包括三个方面:
(1)软件的设计受众,是小孩子、老人、女性,还是专业人士等等;
(2)软件的基本设计原则,以人为本、模块分离、层次清晰、简约至上、适用为先、抽象基本业务等等;
(3)软件编写模式,比如装饰模式、责任链、单件模式等等。
从某种意义上说,设计思想构成了软件的主题。软件原则是我们在开发中的必须遵循的准绳。软件编写模式是开发过程中的重要经验总结。灵活运用设计模式,一方面利于我们编写高质量的代码,另一方面也方便我们对代码进行维护。毕竟对于广大的软件开发者来说,软件的维护时间要比软件编写的时间要多得多。编写过程中,难免要有新的需求,要和别的模块打交道,要对已有的代码进行复用,那么这时候设计模式就派上了用场。我们讨论的主题其实就是设计模式。
讲到设计模式,人们首先想到的语言就是c#或者是java,最不济也是c++,一般来说没有人会考虑到c语言。其实,我认为设计模式就是一种基本思想,过度美化或者神化其实没有必要。其实阅读过linux kernel的朋友都知道,linux虽然自身支持很多的文件系统,但是linux自身很好地把这些系统的基本操作都抽象出来了,成为了基本的虚拟文件系统。
举个例子来说,现在让你写一个音乐播放器,但是要支持的文件格式很多,什么ogg,wav,mp3啊,统统要支持。这时候,你会怎么编写呢?如果用C++语言,你可能会这么写。
class music_file
{
HANDLE hFile;
public:
void music_file() {
}
virtual ~music_file() {
}
virtual void read_file() {
}
virtual void play() {
}
virtual void stop() {
}
virtual void back() {
}
virtual void front() {
}
virtual void up() {
}
virtual void down() {
}
};
其实,你想想看,如果用C语言能够完成相同的抽象操作,那不是效果一样的吗?
typedef struct _music_file
{
HANDLE hFile;
void (*read_file)(struct _music_file* pMusicFile);
void (*play)(struct _music_file* pMusicFile);
void (*stop)(struct _music_file* pMusicFile);
void (*back)(struct _music_file* pMusicFile);
void (*front)(struct _music_file* pMusicFile);
void (*down)(struct _music_file* pMusicFile);
void (*up)(struct _music_file* pMusicFile);
}music_file;
当然,上面的例子比较简单,但是也能说明一些问题。写这篇文章的目的一是希望和朋友们共同学习模式的相关内容,另一方面也希望朋友们能够活学活用,既不要迷信权威,也不要妄自菲薄。只要付出努力,付出汗水,肯定会有收获的。有些大环境你改变不了,那就从改变自己开始。万丈高楼平地起,一步一个脚印才能真真实实学到东西。如果盲目崇拜,言必google、微软、apple,那么除了带来几个唾沫星,还能有什么受用呢?无非白费了口舌而已。
有过面试经验的朋友,或者对设计模式有点熟悉的朋友,都会对单件模式不陌生。对很多面试官而言,单件模式更是他们面试的保留项目。其实,我倒认为,单件模式算不上什么设计模式。最多也就是个技巧。
单件模式要是用C++写,一般这么写。
#include <string.h>
#include <assert.h>
class object
{
public:
static class object* pObject;
static object* create_new_object()
{
if(NULL != pObject)
return pObject;
pObject = new object();
assert(NULL != pObject);
return pObject;
}
private:
object() {
}
~object() {
}
};
class object* object::pObject = NULL;
单件模式的技巧就在于类的构造函数是一个私有的函数。但是类的构造函数又是必须创建的?怎么办呢?那就只有动用static函数了。我们看到static里面调用了构造函数,就是这么简单。
int main(int argc, char* argv[])
{
object* pGlobal = object::create_new_object();
return 1;
}
上面说了C++语言的编写方法,那C语言怎么写?其实也简单。大家也可以试一试。
typedef struct _DATA
{
void* pData;
}DATA;
void* get_data()
{
static DATA* pData = NULL;
if(NULL != pData)
return pData;
pData = (DATA*)malloc(sizeof(DATA));
assert(NULL != pData);
return (void*)pData;
}
原型模式本质上说就是对当前数据进行复制。就像变戏法一样,一个鸽子变成了两个鸽子,两个鸽子变成了三个鸽子,就这么一直变下去。在变的过程中,我们不需要考虑具体的数据类型。为什么呢?因为不同的数据有自己的复制类型,而且每个复制函数都是虚函数。
用C++怎么编写呢,那就是先写一个基类,再编写一个子类。就是这么简单。
class data
{
public:
data () {
}
virtual ~data() {
}
virtual class data* copy() = 0;
};
class data_A : public data
{
public:
data_A() {
}
~data_A() {
}
class data* copy()
{
return new data_A();
}
};
class data_B : public data
{
public:
data_B() {
}
~data_B() {
}
class data* copy()
{
return new data_B();
}
};
那怎么使用呢?其实只要一个通用的调用接口就可以了。
class data* clone(class data* pData)
{
return pData->copy();
}
就这么简单的一个技巧,对C来说,当然也不是什么难事。
typedef struct _DATA
{
struct _DATA* (*copy) (struct _DATA* pData);
}DATA;
假设也有这么一个类型data_A,
DATA data_A = {
data_copy_A};
既然上面用到了这个函数,所以我们也要定义啊。
struct _DATA* data_copy_A(struct _DATA* pData)
{
DATA* pResult = (DATA*)malloc(sizeof(DATA));
assert(NULL != pResult);
memmove(pResult, pData, sizeof(DATA));
return pResult;
};
使用上呢,当然也不含糊。
struct _DATA* clone(struct _DATA* pData)
{
return pData->copy(pData);
};
组合模式听说去很玄乎,其实也并不复杂。为什么?大家可以先想一下数据结构里面的二叉树是怎么回事。为什么就是这么一个简单的二叉树节点既可能是叶节点,也可能是父节点?
typedef struct _NODE
{
void* pData;
struct _NODE* left;
struct _NODE* right;
}NODE;
那什么时候是叶子节点,其实就是left、right为NULL的时候。那么如果它们不是NULL呢,那么很明显此时它们已经是父节点了。那么,我们的这个组合模式是怎么一个情况呢?
typedef struct _Object
{
struct _Object** ppObject;
int number;
void (*operate)(struct _Object* pObject);
}Object;
就是这么一个简单的数据结构,是怎么实现子节点和父节点的差别呢。比如说,现在我们需要对一个父节点的operate进行操作,此时的operate函数应该怎么操作呢?
void operate_of_parent(struct _Object* pObject)
{
int index;
assert(NULL != pObject);
assert(NULL != pObject->ppObject && 0 != pObject->number);
for(index = 0; index < pObject->number; index ++)
{
pObject->ppObject[index]->operate(pObject->ppObject[index]);
}
}
当然,有了parent的operate,也有child的operate。至于是什么操作,那就看自己是怎么操作的了。
void operate_of_child(struct _Object* pObject)
{
assert(NULL != pObject);
printf("child node!\n");
}
父节点也好,子节点也罢,一切的一切都是最后的应用。其实,用户的调用也非常简单,就这么一个简单的函数。
void process(struct Object* pObject)
{
assert(NULL != pObject);
pObject->operate(pObject);
}
模板对于学习C++的同学,其实并不陌生。函数有模板函数,类也有模板类。那么这个模板模式是个什么情况?我们可以思考一下,模板的本质是什么。比如说,现在我们需要编写一个简单的比较模板函数。
template <typename type>
int compare (type a, type b)
{
return a > b ? 1 : 0;
}
模板函数提示我们,只要比较的逻辑是确定的,那么不管是什么数据类型,都会得到一个相应的结果。固然,这个比较的流程比较简单,即使没有采用模板函数也没有关系。但是,要是需要拆分的步骤很多,那么又该怎么办呢?如果相通了这个问题,那么也就明白了什么是template模式。
比方说,现在我们需要设计一个流程。这个流程有很多小的步骤完成。然而,其中每一个步骤的方法是多种多样的,我们可以很多选择。但是,所有步骤构成的逻辑是唯一的,那么我们该怎么办呢?其实也简单。那就是在基类中除了流程函数外,其他的步骤函数全部设置为virtual函数即可。
class basic
{
public:
void basic() {
}
virtual ~basic() {
}
virtual void step1() {
}
virtual void step2() {
}
void process()
{
step1();
step2();
}
};
basic的类说明了基本的流程process是唯一的,所以我们要做的就是对step1和step2进行改写。
class data_A : public basic
{
public:
data_A() {
}
~data_A() {
}
void step1()
{
printf("step 1 in data_A!\n");
}
void step2()
{
printf("step 2 in data_A!\n");
}
};
所以,按照我个人的理解,这里的template主要是一种流程上的统一,细节实现上的分离。明白了这个思想,那么用C语言来描述template模式就不是什么难事了。
typedef struct _Basic
{
void* pData;
void (*step1) (struct _Basic* pBasic);
void (*step2) (struct _Basic* pBasic);
void (*process) (struct _Basic* pBasic);
}Basic;
因为在C++中process函数是直接继承的,C语言下面没有这个机制。所以,对于每一个process来说,process函数都是唯一的,但是我们每一次操作的时候还是要去复制一遍函数指针。而step1和step2是不同的,所以各种方法可以用来灵活修改自己的处理逻辑,没有问题。
void process(struct _Basic* pBasic)
{
pBasic->step1(pBasic);
pBasic->step2(pBasic);
}
工厂模式是比较简单,也是比较好用的一种方式。根本上说,工厂模式的目的就根据不同的要求输出不同的产品。比如说吧,有一个生产鞋子的工厂,它能生产皮鞋,也能生产胶鞋。如果用代码设计,应该怎么做呢?
typedef struct _Shoe
{
int type;
void (*print_shoe)(struct _Shoe*);
}Shoe;
就像上面说的,现在有胶鞋,那也有皮鞋,我们该怎么做呢?
void print_leather_shoe(struct _Shoe* pShoe)
{
assert(NULL != pShoe);
printf("This is a leather show!\n");
}
void print_rubber_shoe(struct _Shoe* pShoe)
{
assert(NULL != pShoe);
printf("This is a rubber shoe!\n");
}
所以,对于一个工厂来说,创建什么样的鞋子,就看我们输入的参数是什么?至于结果,那都是一样的。
#define LEATHER_TYPE 0x01
#define RUBBER_TYPE 0x02
Shoe* manufacture_new_shoe(int type)
{
assert(LEATHER_TYPE == type || RUBBER_TYPE == type);
Shoe* pShoe = (Shoe*)malloc(sizeof(Shoe));
assert(NULL != pShoe);
memset(pShoe, 0, sizeof(Shoe));
if(LEATHER_TYPE == type)
{
pShoe->type == LEATHER_TYPE;
pShoe->print_shoe = print_leather_shoe;
}
else
{
pShoe->type == RUBBER_TYPE;
pShoe->print_shoe = print_rubber_shoe;
}
return pShoe;
}
责任链模式是很实用的一种实际方法。举个例子来说,我们平常在公司里面难免不了报销流程。但是,我们知道公司里面每一级的领导的报批额度是不一样的。比如说,科长的额度是1000元,部长是10000元,总经理是10万元。
那么这个时候,我们应该怎么设计呢?其实可以这么理解。比如说,有人来找领导报销费用了,那么领导可以自己先看看自己能不能报。如果费用可以顺利报下来当然最好,可是万一报不下来呢?那就只能请示领导的领导了。
typedef struct _Leader
{
struct _Leader* next;
int account;
int (*request)(strcut _Leader* pLeader, int num);
}Leader;
所以这个时候,我们首先需要设置额度和领导。
void set_account(struct _Leader* pLeader, int account)
{
assert(NULL != pLeader);
pLeader->account = account;
return;
}
void set_next_leader(const struct _Leader* pLeader, struct _Leader* next)
{
assert(NULL != pLeader && NULL != next);
pLeader->next = next;
return;
}
此时,如果有一个员工过来报销费用,那么应该怎么做呢?假设此时的Leader是经理,报销额度是10万元。所以此时,我们可以看看报销的费用是不是小于10万元?少于这个数就OK,反之就得上报自己的领导了。
int request_for_manager(struct _Leader* pLeader, int num)
{
assert(NULL != pLeader && 0 != num);
if(num < 100000)
return 1;
else if(pLeader->next)
return pLeader->next->request(pLeader->next, num);
else
return 0;
}
前面我们写过的工厂模式实际上是对产品的抽象。对于不同的用户需求,我们可以给予不同的产品,而且这些产品的接口都是一致的。而抽象工厂呢?顾名思义,就是说我们的工厂是不一定的。怎么理解呢,举个例子。
假设有两个水果店都在卖水果,都卖苹果和葡萄。其中一个水果店买白苹果和白葡萄,另外一个水果店卖红苹果和红葡萄。所以说,对于水果店而言,尽管都在卖水果,但是两个店卖的品种不一样。
既然水果不一样,那我们先定义水果。
typedef struct _Apple
{
void (*print_apple)();
}Apple;
typedef struct _Grape
{
void (*print_grape)();
}Grape;
上面分别对苹果和葡萄进行了抽象,当然它们的具体函数也是不一样的。
void print_white_apple()
{
printf("white apple!\n");
}
void print_red_apple()
{
printf("red apple!\n");
}
void print_white_grape()
{
printf("white grape!\n");
}
void print_red_grape()
{
printf("red grape!\n");
}
完成了水果函数的定义。下面就该定义工厂了,和水果一样,我们也需要对工厂进行抽象处理。
typedef struct _FruitShop
{
Apple* (*sell_apple)();
Apple* (*sell_grape)();
}FruitShop;
所以,对于卖白苹果、白葡萄的水果店就该这样设计了,红苹果、红葡萄的水果店亦是如此。
Apple* sell_white_apple()
{
Apple* pApple = (Apple*) malloc(sizeof(Apple));
assert(NULL != pApple);
pApple->print_apple = print_white_apple;
return pApple;
}
Grape* sell_white_grape()
{
Grape* pGrape = (Grape*) malloc(sizeof(Grape));
assert(NULL != pGrape);
pGrape->print_grape = print_white_grape;
return pGrape;
}
这样,基本的框架就算搭建完成的,以后创建工厂的时候,
FruitShop* create_fruit_shop(int color)
{
FruitShop* pFruitShop = (FruitShop*) malloc(sizeof(FruitShop));
assert(NULL != pFruitShop);
if(WHITE == color)
{
pFruitShop->sell_apple = sell_white_apple;
pFruitShop->sell_grape = sell_white_grape;
}
else
{
pFruitShop->sell_apple = sell_red_apple;
pFruitShop->sell_grape = sell_red_grape;
}
return pFruitShop;
}
使用过C++的朋友大概对迭代器模式都不会太陌生。这主要是因为我们在编写代码的时候离不开迭代器,队列有迭代器,向量也有迭代器。那么,为什么要迭代器呢?这主要是为了提炼一种通用的数据访问方法。
比如说,现在有一个数据的容器,
typedef struct _Container
{
int* pData;
int size;
int length;
Interator* (*create_new_interator)(struct _Container* pContainer);
int (*get_first)(struct _Container* pContainer);
int (*get_last)(struct _Container* pContainer);
}Container;
我们看到,容器可以创建迭代器。那什么是迭代器呢?
typedef struct _Interator
{
void* pVector;
int index;
int(* get_first)(struct _Interator* pInterator);
int(* get_last)(struct _Interator* pInterator);
}Interator;
我们看到,容器有get_first,迭代器也有get_first,这中间有什么区别?
int vector_get_first(struct _Container* pContainer)
{
assert(NULL != pContainer);
return pContainer->pData[0];
}
int vector_get_last(struct _Container* pContainer)
{
assert(NULL != pContainer);
return pContainer->pData[pContainer->size -1];
}
int vector_interator_get_first(struct _Interator* pInterator)
{
Container* pContainer;
assert(NULL != pInterator && NULL != pInterator->pVector);
pContainer = (struct _Container*) (pInterator->pVector);
return pContainer ->get_first(pContainer);
}
int vector_interator_get_last(struct _Interator* pInterator)
{
Container* pContainer;
assert(NULL != pInterator && NULL != pInterator->pVector);
pContainer = (struct _Container*) (pInterator->pVector);
return pContainer ->get_last(pContainer);
}
看到上面的代码之后,我们发现迭代器的操作实际上也是对容器的操作而已。
外观模式是比较简单的模式。它的目的也是为了简单。什么意思呢?举个例子吧。以前,我们逛街的时候吃要到小吃一条街,购物要到购物一条街,看书、看电影要到文化一条街。那么有没有这样的地方,既可以吃喝玩乐,同时相互又靠得比较近呢。其实,这就是悠闲广场,遍布全国的万达广场就是干了这么一件事。
首先,我们原来是怎么做的。
typedef struct _FoodSteet
{
void (*eat)();
}FoodStreet;
void eat()
{
printf("eat here!\n");
}
typedef struct _ShopStreet
{
void (*buy)();
}ShopStreet;
void buy()
{
printf("buy here!\n");
}
typedef struct _BookStreet
{
void (*read)();
}BookStreet;
void read()
{
printf("read here");
}
下面,我们就要在一个plaza里面完成所有的项目,怎么办呢?
typedef struct _Plaza
{
FoodStreet* pFoodStreet;
ShopStreet* pShopStreet;
BookStreet* pBookStreet;
void (*play)(struct _Plaza* pPlaza);
}Plaza;
void play(struct _Plaza* pPlaza)
{
assert(NULL != pPlaza);
pPlaza->pFoodStreet->eat();
pPlaza->pShopStreet->buy();
pPlaza->pBookStreet->read();
}
代理模式是一种比较有意思的设计模式。它的基本思路也不复杂。举个例子来说,以前在学校上网的时候,并不是每一台pc都有上网的权限的。比如说,现在有pc1、pc2、pc3,但是只有pc1有上网权限,但是pc2、pc3也想上网,此时应该怎么办呢?
此时,我们需要做的就是在pc1上开启代理软件,同时把pc2、pc3的IE代理指向pc1即可。这个时候,如果pc2或者pc3想上网,那么报文会先指向pc1,然后pc1把Internet传回的报文再发给pc2或者pc3。这样一个代理的过程就完成了整个的上网过程。
在说明完整的过程之后,我们可以考虑一下软件应该怎么编写呢?
typedef struct _PC_Client
{
void (*request)();
}PC_Client;
void ftp_request()
{
printf("request from ftp!\n");
}
void http_request()
{
printf("request from http!\n");
}
void smtp_request()
{
printf("request from smtp!\n");
}
这个时候,代理的操作应该怎么写呢?怎么处理来自各个协议的请求呢?
typedef struct _Proxy
{
PC_Client* pClient;
}Proxy;
void process(Proxy* pProxy)
{
assert(NULL != pProxy);
pProxy->pClient->request();
}
享元模式看上去有点玄乎,但是其实也没有那么复杂。我们还是用示例说话。比如说,大家在使用电脑的使用应该少不了使用WORD软件。使用WORD呢, 那就少不了设置模板。什么模板呢,比如说标题的模板,正文的模板等等。这些模板呢,又包括很多的内容。哪些方面呢,比如说字体、标号、字距、行距、大小等等。
typedef struct _Font
{
int type;
int sequence;
int gap;
int lineDistance;
void (*operate)(struct _Font* pFont);
}Font;
上面的Font表示了各种Font的模板形式。所以,下面的方法就是定制一个FontFactory的结构。
Font* GetFont(struct _FontFactory* pFontFactory, int type, int sequence, int gap, int lineDistance)
{
int index;
Font* pFont;
Font* ppFont;
if(NULL == pFontFactory)
return NULL;
for(index = 0; index < pFontFactory->number; index++)
{
if(type != pFontFactory->ppFont[index]->type)
continue;
if(sequence != pFontFactory->ppFont[index]->sequence)
continue;
if(gap != pFontFactory->ppFont[index]->gap)
continue;
if(lineDistance != pFontFactory->ppFont[index]->lineDistance)
continue;
return pFontFactory->ppFont[index];
}
pFont = (Font*)malloc(sizeof(Font));
assert(NULL != pFont);
pFont->type = type;
pFont->sequence = sequence;
pFont->gap = gap;
pFont->lineDistance = lineDistance;
if(pFontFactory-> number < pFontFactory->size)
{
pFontFactory->ppFont[index] = pFont;
pFontFactory->number ++;
return pFont;
}
ppFont = (Font**)malloc(sizeof(Font*) * pFontFactory->size * 2);
assert(NULL != ppFont);
memmove(ppFont, pFontFacoty->ppFont, pFontFactory->size);
free(pFontFactory->ppFont);
pFontFactory->size *= 2;
pFontFactory->number ++;
ppFontFactory->ppFont = ppFont;
return pFont;
}
装饰模式是比较好玩,也比较有意义。其实就我个人看来,它和责任链还是蛮像的。只不过一个是比较判断,一个是迭代处理。装饰模式就是那种迭代处理的模式,关键在哪呢?我们可以看看数据结构。
typedef struct _Object
{
struct _Object* prev;
void (*decorate)(struct _Object* pObject);
}Object;
装饰模式最经典的地方就是把pObject这个值放在了数据结构里面。当然,装饰模式的奥妙还不仅仅在这个地方,还有一个地方就是迭代处理。我们可以自己随便写一个decorate函数试试看,
void decorate(struct _Object* pObeject)
{
assert(NULL != pObject);
if(NULL != pObject->prev)
pObject->prev->decorate(pObject->prev);
printf("normal decorate!\n");
}
所以,装饰模式的最重要的两个方面就体现在:prev参数和decorate迭代处理。
现在的生活当中,我们离不开各种电子工具。什么笔记本电脑、手机、mp4啊,都离不开充电。既然是充电,那么就需要用到充电器。其实从根本上来说,充电器就是一个个普通的适配器。什么叫适配器呢,就是把220v、50hz的交流电压编程5~12v的直流电压。充电器就干了这么一件事情。
那么,这样的一个充电适配器,我们应该怎么用c++描述呢?
class voltage_12v
{
public:
voltage_12v() {
}
virtual ~voltage_12v() {
}
virtual void request() {
}
};
class v220_to_v12
{
public:
v220_to_v12() {
}
~v220_to_v12() {
}
void voltage_transform_process() {
}
};
class adapter: public voltage_12v
{
v220_to_v12* pAdaptee;
public:
adapter() {
}
~adapter() {
}
void request()
{
pAdaptee->voltage_transform_process();
}
};
通过上面的代码,我们其实可以这样理解。类voltage_12v表示我们的最终目的就是为了获得一个12v的直流电压。当然获得12v可以有很多的方法,利用适配器转换仅仅是其中的一个方法。adapter表示适配器,它自己不能实现220v到12v的转换工作,所以需要调用类v220_to_v12的转换函数。所以,我们利用adapter获得12v的过程,其实就是调用v220_to_v12函数的过程。
不过,既然我们的主题是用c语言来编写适配器模式,那么我们就要实现最初的目标。这其实也不难,关键一步就是定义一个Adapter的数据结构。然后把所有的Adapter工作都由Adaptee来做,就是这么简单。不知我说明白了没有?
typdef struct _Adaptee
{
void (*real_process)(struct _Adaptee* pAdaptee);
}Adaptee;
typedef struct _Adapter
{
void* pAdaptee;
void (*transform_process)(struct _Adapter* pAdapter);
}Adapter;
策略模式就是用统一的方法接口分别对不同类型的数据进行访问。比如说,现在我们想用pc看一部电影,此时应该怎么做呢?看电影嘛,当然需要各种播放电影的方法。rmvb要rmvb格式的方法,avi要avi的方法,mpeg要mpeg的方法。可是事实上,我们完全可以不去管是什么文件格式。因为播放器对所有的操作进行了抽象,不同的文件会自动调用相应的访问方法。
typedef struct _MoviePlay
{
struct _CommMoviePlay* pCommMoviePlay;
}MoviePlay;
typedef struct _CommMoviePlay
{
HANDLE hFile;
void (*play)(HANDLE hFile);
}CommMoviePlay;
这个时候呢,对于用户来说,统一的文件接口就是MoviePlay。接下来的一个工作,就是编写一个统一的访问接口。
void play_movie_file(struct MoviePlay* pMoviePlay)
{
CommMoviePlay* pCommMoviePlay;
assert(NULL != pMoviePlay);
pCommMoviePlay = pMoviePlay->pCommMoviePlay;
pCommMoviePlay->play(pCommMoviePlay->hFile);
}
最后的工作就是对不同的hFile进行play的实际操作,写简单一点就是,
void play_avi_file(HANDLE hFile)
{
printf("play avi file!\n");
}
void play_rmvb_file(HANDLE hFile)
{
printf("play rmvb file!\n");
}
void play_mpeg_file(HANDLE hFile)
{
printf("play mpeg file!\n");
}
中介者模式,听上去有一点陌生。但是,只要我给朋友们打个比方就明白了。早先自由恋爱没有现在那么普遍的时候,男女之间的相识还是需要通过媒婆之间才能相互认识。男孩对女方有什么要求,可以通过媒婆向女方提出来;当然,女方有什么要求也可以通过媒婆向男方提出来。所以,中介者模式在我看来,就是媒婆模式。
typedef struct _Mediator
{
People* man;
People* woman;
}Mediator;
上面的数据结构是给媒婆的,那么当然还有一个数据结构是给男方、女方的。
typedef struct _People
{
Mediator* pMediator;
void (*request)(struct _People* pPeople);
void (*process)(struct _Peoplle* pPeople);
}People;
所以,这里我们看到的如果是男方的要求,那么这个要求应该女方去处理啊,怎么处理呢?
void man_request(struct _People* pPeople)
{
assert(NULL != pPeople);
pPeople->pMediator->woman->process(pPeople->pMediator->woman);
}
上面做的是男方向女方提出的要求,所以女方也可以向男方提要求了。毕竟男女平等嘛。
void woman_request(struct _People* pPeople)
{
assert(NULL != pPeople);
pPeople->pMediator->man->process(pPeople->pMediator->man);
}
如果说前面的工厂模式是对接口进行抽象化处理,那么建造者模式更像是对流程本身的一种抽象化处理。这话怎么理解呢?大家可以听我慢慢到来。以前买电脑的时候,大家都喜欢自己组装机器。一方面可以满足自己的个性化需求,另外一方面也可以在价格上得到很多实惠。但是电脑是由很多部分组成的,每个厂家都只负责其中的一部分,而且相同的组件也有很多的品牌可以从中选择。这对于我们消费者来说当然非常有利,那么应该怎么设计呢?
typedef struct _AssemblePersonalComputer
{
void (*assemble_cpu)();
void (*assemble_memory)();
void (*assemble_harddisk)();
}AssemblePersonalComputer;
对于一个希望配置intel cpu,samsung 内存、日立硬盘的朋友。他可以这么设计,
void assemble_intel_cpu()
{
printf("intel cpu!\n");
}
void assemble_samsung_memory()
{
printf("samsung memory!\n");
}
void assemble_hitachi_harddisk()
{
printf("hitachi harddisk!\n");
}
而对于一个希望配置AMD cpu, kingston内存、西部数据硬盘的朋友。他又该怎么做呢?
void assemble_amd_cpu()
{
printf("amd cpu!\n");
}
void assemble_kingston_memory()
{
printf("kingston memory!\n");
}
void assmeble_western_digital_harddisk()
{
printf("western digital harddisk!\n");
}
在以往的软件开发过程中,我们总是强调模块之间要低耦合,模块本身要高内聚。那么,可以通过哪些设计模式来实现呢?桥接模式就是不错的一个选择。我们知道,在现实的软件开发过程当中,用户的要求是多种多样的。比如说,有这么一个饺子店吧。假设饺子店原来只卖肉馅的饺子,可是后来一些吃素的顾客说能不能做一些素的饺子。听到这些要求的老板自然不敢怠慢,所以也开始卖素饺子。之后,又有顾客提出,现在的肉馅饺子只有猪肉的,能不能做点牛肉、羊肉馅的饺子?一些只吃素的顾客也有意见了,他们建议能不能增加一些素馅饺子的品种,什么白菜馅的、韭菜馅的,都可以做一点。由此看来,顾客的要求是一层一层递增的。关键是我们如何把顾客的要求和我们的实现的接口进行有效地分离呢?
其实我们可以这么做,通常的产品还是按照共同的属性进行归类。
typedef struct _MeatDumpling
{
void (*make)();
}MeatDumpling;
typedef struct _NormalDumpling
{
void (*make)();
}NormalDumpling;
上面只是对饺子进行归类。第一类是对肉馅饺子的归类,第二类是对素馅饺子的归类,这些地方都没有什么特别之处。那么,关键是我们怎么把它和顾客的要求联系在一起呢?
typedef struct _DumplingReuqest
{
int type;
void* pDumpling;
}DumplingRequest;
这里定义了一个饺子买卖的接口。它的特别支持就在于两个地方,第一是我们定义了饺子的类型type,这个type是可以随便扩充的;第二就是这里的pDumpling是一个void*指针,只有把它和具体的dumpling绑定才会衍生出具体的含义。
void buy_dumpling(DumplingReuqest* pDumplingRequest)
{
assert(NULL != pDumplingRequest);
if(MEAT_TYPE == pDumplingRequest->type)
return (MeatDumpling*)(pDumplingRequest->pDumpling)->make();
else
return (NormalDumpling*)(pDumplingRequest->pDumpling)->make();
}
观察者模式可能是我们在软件开发中使用得比较多的一种设计模式。为什么这么说?大家可以听我一一到来。我们知道,在windows的软件中,所有的界都是由窗口构成的。对话框是窗口,菜单是窗口,工具栏也是窗口。那么这些窗口,在很多情况下要对一些共有的信息进行处理。比如说,窗口的放大,窗口的减小等等。面对这一情况,观察者模式就是不错的一个选择。
首先,我们可以对这些共有的object进行提炼。
typedef struct _Object
{
observer* pObserverList[MAX_BINDING_NUMBER];
int number;
void (*notify)(struct _Object* pObject);
void (*add_observer)(observer* pObserver);
void (*del_observer)(observer* pObserver);
}Object;
其实,我们需要定义的就是观察者本身了。就像我们前面说的一样,观察者可以是菜单、工具栏或者是子窗口等等。
typedef struct _Observer
{
Object* pObject;
void (*update)(struct _Observer* pObserver);
}Observer;
紧接着,我们要做的就是在Observer创建的时候,把observer自身绑定到Object上面。
void bind_observer_to_object(Observer* pObserver, Object* pObject)
{
assert(NULL != pObserver && NULL != pObject);
pObserver->pObject = pObject;
pObject->add_observer(pObserver);
}
void unbind_observer_from_object(Observer* pObserver, Object* pObject)
{
assert(NULL != pObserver && NULL != pObject);
pObject->del_observer(observer* pObserver);
memset(pObserver, 0, sizeof(Observer));
}
既然Observer在创建的时候就把自己绑定在某一个具体的Object上面,那么Object发生改变的时候,统一更新操作就是一件很容易的事情了。
void notify(struct _Object* pObject)
{
Obserer* pObserver;
int index;
assert(NULL != pObject);
for(index = 0; index < pObject->number; index++)
{
pObserver = pObjecet->pObserverList[index];
pObserver->update(pObserver);
}
}
备忘录模式的起源来自于撤销的基本操作。有过word软件操作经验的朋友,应该基本上都使用过撤销的功能。举个例子,假设你不小心删除了好几个段落的文字,这时候你应该怎么办呢?其实要做的很简单,单击一些【撤销】就可以全部搞定了。撤销按钮给我们提供了一次反悔的机会。
既然是撤销,那么我们在进行某种动作的时候,就应该创建一个相应的撤销操作?这个撤销操作的相关定义可以是这样的。
typedef struct _Action
{
int type;
struct _Action* next;
void* pData;
void (*process)(void* pData);
}Action;
数据结构中定义了两个部分:撤销的数据、恢复的操作。那么这个撤销函数应该有一个创建的函数,还有一个恢复的函数。所以,作为撤销动作的管理者应该包括,
typedef struct _Organizer
{
int number;
Action* pActionHead;
Action* (*create)();
void (*restore)(struct _Organizer* pOrganizer);
}Organizer;
既然数据在创建和修改的过程中都会有相应的恢复操作,那么要是真正恢复原来的数据也就变得非常简单了。
void restore(struct _Organizer* pOrganizer)
{
Action* pHead;
assert(NULL != pOrganizer);
pHead = pOrganizer->pActionHead;
pHead->process(pHead->pData);
pOrganizer->pActionHead = pHead->next;
pOrganizer->number --;
free(pHead);
return;
}
解释器模式虽然听上去有些费解,但是如果用示例说明一下就不难理解了。我们知道在C语言中,关于变量的定义是这样的:一个不以数字开始的由字母、数字和下划线构成的字符串。这种形式的表达式可以用状态自动机解决,当然也可以用解释器的方式解决。
typedef struct _Interpret
{
int type;
void* (*process)(void* pData, int* type, int* result);
}Interpret;
上面的数据结构比较简单,但是很能说明问题。就拿变量来说吧,这里就可以定义成字母的解释器、数字解释器、下划线解释器三种形式。所以,我们可以进一步定义一下process的相关函数。
#define DIGITAL_TYPE 1
#define LETTER_TYPE 2
#define BOTTOM_LINE 3
void* digital_process(void* pData, int* type, int* result)
{
UINT8* str;
assert(NULL != pData && NULL != type && NULL != result);
str = (UNT8*)pData;
while (*str >= '0' && *str <= '9')
{
str ++;
}
if(*str == '\0')
{
*result = TRUE;
return NULL;
}
if(*str == '_')
{
*result = TRUE;
*type = BOTTOM_TYPE;
return str;
}
if(*str >= 'a' && *str <= 'z' || *str >= 'A' && *str <= 'Z')
{
*result = TRUE;
*type = LETTER_TYPE;
return str;
}
*result = FALSE;
return NULL;
}
void* letter_process(void* pData, int* type, int* result)
{
UINT8* str;
assert(NULL != pData && NULL != type && NULL != result);
str = (UNT8*)pData;
while (*str >= 'a' && *str <= 'z' || *str >= 'A' && *str <= 'Z')
{
str ++;
}
if(*str == '\0')
{
*result = TRUE;
return NULL;
}
if(*str == '_')
{
*result = TRUE;
*type = BOTTOM_TYPE;
return str;
}
if(*str >= '0' && *str <= '9')
{
*result = TRUE;
*type = DIGITAL_TYPE;
return str;
}
*result = FALSE;
return NULL;
}
void* bottom_process(void* pData, int* type, int* result)
{
UINT8* str;
assert(NULL != pData && NULL != type && NULL != result);
str = (UNT8*)pData;
while ('_' == *str )
{
str ++;
}
if(*str == '\0')
{
*result = TRUE;
return NULL;
}
if(*str >= 'a' && *str <= 'z' || *str >= 'A' && *str <= 'Z')
{
*result = TRUE;
*type = LETTER_TYPE;
return str;
}
if(*str >= '0' && *str <= '9')
{
*result = TRUE;
*type = DIGITAL_TYPE;
return str;
}
*result = FALSE;
return NULL;
}
命令模式的目的主要是为了把命令者和执行者分开。老规矩,举个范例吧。假设李老板是一家公司的头儿,他现在让他的秘书王小姐去送一封信。王小姐当然不会自己亲自把信送到目的地,她会把信交给邮局来完成整个投递的全过程。现在,我们就对投递者、命令、发令者分别作出定义。
首先定义post的相关数据。
typedef struct _Post
{
void (*do)(struct _Post* pPost);
}Post;
Post完成了实际的投递工作,那么命令呢?
typedef struct _Command
{
void* pData;
void (*exe)(struct _Command* pCommand);
}Command;
void post_exe(struct _Command* pCommand)
{
assert(NULL != pCommand);
(Post*)(pCommand->pData)->do((Post*)(pCommand->pData));
return;
}
我们看到了Post、Command的操作,那么剩下的就是boss的定义了。
typedef struct _Boss
{
Command* pCommand;
void (*call)(struct _Boss* pBoss);
}Boss;
void boss_call(struct _Boss* pBoss)
{
assert(NULL != pBoss);
pBoss->pCommand->exe(pBoss->pCommand);
return;
}
状态模式是协议交互中使用得比较多的模式。比如说,在不同的协议中,都会存在启动、保持、中止等基本状态。那么怎么灵活地转变这些状态就是我们需要考虑的事情。假设现在有一个state,
typedef struct _State
{
void (*process)();
struct _State* (*change_state)();
}State;
说明一下,这里定义了两个变量,分别process函数和change_state函数。其中proces函数就是普通的数据操作,
void normal_process()
{
printf("normal process!\n");
}
change_state函数本质上就是确定下一个状态是什么。
struct _State* change_state()
{
State* pNextState = NULL;
pNextState = (struct _State*)malloc(sizeof(struct _State));
assert(NULL != pNextState);
pNextState ->process = next_process;
pNextState ->change_state = next_change_state;
return pNextState;
}
所以,在context中,应该有一个state变量,还应该有一个state变换函数。
typedef struct _Context
{
State* pState;
void (*change)(struct _Context* pContext);
}Context;
void context_change(struct _Context* pContext)
{
State* pPre;
assert(NULL != pContext);
pPre = pContext->pState;
pContext->pState = pPre->changeState();
free(pPre);
return;
}
不知不觉当中,我们就到了最后一种设计模式,即访问者模式。访问者模式,听上去复杂一些。但是,这种模式用简单的一句话说,就是不同的人对不同的事物有不同的感觉。比如说吧,豆腐可以做成麻辣豆腐,也可以做成臭豆腐。可是,不同的地方的人未必都喜欢这两种豆腐。四川的朋友可能更喜欢辣豆腐,江浙的人就可能对臭豆腐更喜欢一些。那么,这种情况应该怎么用设计模式表达呢?
typedef struct _Tofu
{
int type;
void (*eat) (struct _Visitor* pVisitor, struct _Tofu* pTofu);
}Tofu;
typedef struct _Visitor
{
int region;
void (*process)(struct _Tofu* pTofu, struct _Visitor* pVisitor);
}Visitor;
就是这样一个豆腐,eat的时候就要做不同的判断了。
void eat(struct _Visitor* pVisitor, struct _Tofu* pTofu)
{
assert(NULL != pVisitor && NULL != pTofu);
pVisitor->process(pTofu, pVisitor);
}
既然eat的操作最后还是靠不同的visitor来处理了,那么下面就该定义process函数了。
void process(struct _Tofu* pTofu, struct _Visitor* pVisitor)
{
assert(NULL != pTofu && NULL != pVisitor);
if(pTofu->type == SPICY_FOOD && pVisitor->region == WEST ||
pTofu->type == STRONG_SMELL_FOOD && pVisitor->region == EAST)
{
printf("I like this food!\n");
return;
}
printf("I hate this food!\n");
}
记得还在我们大学C++第一门课的时候,老师就告诉我们说,C++是一门面向对象的语言。C++有三个最重要的特点,即继承、封装、多态。等到后来随着编码的增多和工作经验的积累,我也慢慢明白了面向对象的含义。可是,等我工作以后,使用的编程语言更多的是C语言,这时候我又想能不能把C语言变成面向对象的语言呢?等到后来通过思考和实践,我发现其实C语言也是可以面向对象的,也是可以应用设计模式的,关键就在于如何实现面向对象语言的三个重要属性。
(1)继承性
typedef struct _parent
{
int data_parent;
}Parent;
typedef struct _Child
{
struct _parent parent;
int data_child;
}Child;
在设计C语言继承性的时候,我们需要做的就是把基础数据放在继承的结构的首位置即可。这样,不管是数据的访问、数据的强转、数据的访问都不会有什么问题。
(2)封装性
struct _Data;
typedef void (*process)(struct _Data* pData);
typedef struct _Data
{
int value;
process pProcess;
}Data;
封装性的意义在于,函数和数据是绑在一起的,数据和数据是绑在一起的。这样,我们就可以通过简单的一个结构指针访问到所有的数据,遍历所有的函数。封装性,这是类拥有的属性,当然也是数据结构体拥有的属性。
(3)多态
typedef struct _Play
{
void* pData;
void (*start_play)(struct _Play* pPlay);
}Play;
多态,就是说用同一的接口代码处理不同的数据。比如说,这里的Play结构就是一个通用的数据结构,我们也不清楚pData是什么数据,start_play是什么处理函数?但是,我们处理的时候只要调用pPlay->start_play(pPlay)就可以了。剩下来的事情我们不需要管,因为不同的接口会有不同的函数去处理,我们只要学会调用就可以了。
文章浏览阅读3.2k次。本文研究全球与中国市场分布式光纤传感器的发展现状及未来发展趋势,分别从生产和消费的角度分析分布式光纤传感器的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特点、产品规格、不同规格产品的价格、产量、产值及全球和中国市场主要生产商的市场份额。主要生产商包括:FISO TechnologiesBrugg KabelSensor HighwayOmnisensAFL GlobalQinetiQ GroupLockheed MartinOSENSA Innovati_预计2026年中国分布式传感器市场规模有多大
文章浏览阅读1.1k次,点赞2次,收藏12次。常用组合逻辑电路结构——为IC设计的延时估计铺垫学习目的:估计模块间的delay,确保写的代码的timing 综合能给到多少HZ,以满足需求!_基4布斯算法代码
文章浏览阅读3.3k次,点赞3次,收藏5次。OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版
文章浏览阅读2.2k次。USACO自1992年举办,到目前为止已经举办了27届,目的是为了帮助美国信息学国家队选拔IOI的队员,目前逐渐发展为全球热门的线上赛事,成为美国大学申请条件下,含金量相当高的官方竞赛。USACO的比赛成绩可以助力计算机专业留学,越来越多的学生进入了康奈尔,麻省理工,普林斯顿,哈佛和耶鲁等大学,这些同学的共同点是他们都参加了美国计算机科学竞赛(USACO),并且取得过非常好的成绩。适合参赛人群USACO适合国内在读学生有意向申请美国大学的或者想锻炼自己编程能力的同学,高三学生也可以参加12月的第_usaco可以多次提交吗
文章浏览阅读394次。1.1 存储程序1.2 创建存储过程1.3 创建自定义函数1.3.1 示例1.4 自定义函数和存储过程的区别1.5 变量的使用1.6 定义条件和处理程序1.6.1 定义条件1.6.1.1 示例1.6.2 定义处理程序1.6.2.1 示例1.7 光标的使用1.7.1 声明光标1.7.2 打开光标1.7.3 使用光标1.7.4 关闭光标1.8 流程控制的使用1.8.1 IF语句1.8.2 CASE语句1.8.3 LOOP语句1.8.4 LEAVE语句1.8.5 ITERATE语句1.8.6 REPEAT语句。_mysql自定义函数和存储过程
文章浏览阅读188次。半导体二极管——集成电路最小组成单元。_本征半导体电流为0
文章浏览阅读2.8k次,点赞3次,收藏18次。游戏水面特效实现方式太多。咱们这边介绍的是一最简单的UV动画(无顶点位移),整个mesh由4个顶点构成。实现了水面效果(左图),不动代码稍微修改下参数和贴图可以实现岩浆效果(右图)。有要思路是1,uv按时间去做正弦波移动2,在1的基础上加个凹凸图混合uv3,在1、2的基础上加个水流方向4,加上对雾效的支持,如没必要请自行删除雾效代码(把包含fog的几行代码删除)S..._unity 岩浆shader
文章浏览阅读5k次。广义线性模型是线性模型的扩展,它通过连接函数建立响应变量的数学期望值与线性组合的预测变量之间的关系。广义线性模型拟合的形式为:其中g(μY)是条件均值的函数(称为连接函数)。另外,你可放松Y为正态分布的假设,改为Y 服从指数分布族中的一种分布即可。设定好连接函数和概率分布后,便可以通过最大似然估计的多次迭代推导出各参数值。在大部分情况下,线性模型就可以通过一系列连续型或类别型预测变量来预测正态分布的响应变量的工作。但是,有时候我们要进行非正态因变量的分析,例如:(1)类别型.._广义线性回归模型
文章浏览阅读69次。环境保护、 保护地球、 校园环保、垃圾分类、绿色家园、等网站的设计与制作。 总结了一些学生网页制作的经验:一般的网页需要融入以下知识点:div+css布局、浮动、定位、高级css、表格、表单及验证、js轮播图、音频 视频 Flash的应用、ul li、下拉导航栏、鼠标划过效果等知识点,网页的风格主题也很全面:如爱好、风景、校园、美食、动漫、游戏、咖啡、音乐、家乡、电影、名人、商城以及个人主页等主题,学生、新手可参考下方页面的布局和设计和HTML源码(有用点赞△) 一套A+的网_垃圾分类网页设计目标怎么写
文章浏览阅读614次,点赞7次,收藏11次。之前找到一个修改 exe 中 DLL地址 的方法, 不太好使,虽然能正确启动, 但无法改变 exe 的工作目录,这就影响了.Net 中很多获取 exe 执行目录来拼接的地址 ( 相对路径 ),比如 wwwroot 和 代码中相对目录还有一些复制到目录的普通文件 等等,它们的地址都会指向原来 exe 的目录, 而不是自定义的 “lib” 目录,根本原因就是没有修改 exe 的工作目录这次来搞一个启动程序,把 .net 的所有东西都放在一个文件夹,在文件夹同级的目录制作一个 exe._.net dll 全局目录
文章浏览阅读1.5k次。本文为转载,原博客地址:http://blog.csdn.net/hujingshuang/article/details/46910259简介 BRIEF是2010年的一篇名为《BRIEF:Binary Robust Independent Elementary Features》的文章中提出,BRIEF是对已检测到的特征点进行描述,它是一种二进制编码的描述子,摈弃了利用区域灰度..._breif description calculation 特征点
文章浏览阅读4.1k次,点赞21次,收藏79次。本文是《基于SpringBoot的房屋租赁管理系统》的配套原创说明文档,可以给应届毕业生提供格式撰写参考,也可以给开发类似系统的朋友们提供功能业务设计思路。_基于spring boot的房屋租赁系统论文