`
jinghuainfo
  • 浏览: 1520212 次
  • 性别: Icon_minigender_2
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

Builder模式实例分析(C语言版)

 
阅读更多

Builder模式实例分析(C语言版)

转载时请注明出处:http://blog.csdn.net/absurd

设计模式、设计模式还是设计模式,设计模式已经被许多高手讲过了无数遍了。本来我无意再去重复被人重复过无数遍的工作,但按照我们的培训计划,现在该讲设计模式了,作为培训计划的制定者,我不能不贡献一点力量,所以最终决定写几篇关于设计模式的BLOG。本文的主题是Builder模式。

Builder模式是一个很常用模式。按照《设计模式》一书所说,它的意图是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。这句话有点深奥,构建、创建和表示,这些术语往往让新手摸不着头脑。要解释这些术语恐怕有困难,算了,还是让我们来打个比方吧:建筑物是一个复杂对象,砖头、钢筋和水泥是建筑的基本材料,生产建筑材料的过程就是所谓的构建,修建建筑物的过程就是所谓的创建,建筑物的风格和种类就是所谓的表示。在这里,说白了,builder模式就是让制砖厂和建筑公司分开,由建筑公司决定建筑物的风格和种类,让同样的砖头可以修建不同的建筑物。

我们再来看一个实际一点例子:XML是一种广泛使用的文件格式,和其它任何文件格式一样,它本身只是一个容器。你可以用它来存放配置文件(gconf),可以用它存放多媒体文件(SMIL),也可以用它存放Office文档(OpenOffice)XML只表示语法上的内容,而不表示语义上的内容,所以你可以用它存放任何数据。操作XML最正规的方式是文档对象模型(DOM),不过这个东西太复杂了,除非为了做XSLT转换,否则很少有人用它。

操作XML文件最简单的方式是SAX(Simple API for XML)SAX方式采用了典型的Builder模式,它把XML解析创建表示两个过程分开了,并且只负责解析过程,由用户自己负责创建表示的过程。如果用SAX方式解析XML文件,并生成一棵DOM树,那么解析的过程就是前面所说的构建过程,而生成DOM树的过程就是创建表示的过程。为了避免在术语上绕来绕去,在后面的例子中,我干脆用解析和创建两个词来代替它们。

XML文件的结构是递归的,它由tag和文本等基本元素组成。tag有起始tag(<html>),结束tag(</html>)和起始/结束tag(<html/>)SAX解析器(Parser)会解析出这些基本的元素,但并不把这些元素构建成一棵DOM树,而是把这些基本元素交给创建者Builder处理。至于Builder拿这些元素去创建什么,SAX解析器是不管的。不同的Builder会做不同的事情,有的Builder可能会用这些基本元素构建成一棵DOM树,有的Builder可能只是为了取出某个tag的属性,而有的Builder可能会把OpenOffice文档转换成PDF文档。总之,Builder可以实现不同的功能,但解析器始终是被重用的。

SAX例子中,我们可以看出:采用Builder模式,可以重用解析器部分的代码,把变化的部分隔离到Builder里,这是从代码重用和隔离变化的角度考虑的。另外一方面,分离解析和创建两个过程,实际上也简化了问题的复杂度,解析器部分只负责解析,实现起来更加容易。而且创建者Builder更了解自己的目标,会做得更专业。

Builder的模式的结构:

o_builder_pattern

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 414.75pt; HEIGHT: 146.25pt" o:ole="" type="#_x0000_t75"><imagedata o:title="" src="file:///C:/DOCUME~1/q/LOCALS~1/Temp/msoclip1/06/clip_image001.gif"></imagedata></shape>

在上面的SAX例子中,Director就是解析器(Parser),而BuilderSAX要求提供的一个抽象接口,ConcreteBuilderBuilder接口的具体实现。比如某个ConcreteBuilder的功能可能是创建一棵DOM树,另外一个ConcreteBuilder的功能可能是把OpenOffice文档转换为PDF文档,总之,Director不关心Builder的具体实现。

Builder的模式的交互过程:

<shape id="_x0000_i1026" style="WIDTH: 414.75pt; HEIGHT: 235.5pt" o:ole="" type="#_x0000_t75"><imagedata o:title="" src="file:///C:/DOCUME~1/q/LOCALS~1/Temp/msoclip1/06/clip_image002.png"><font size="3"></font></imagedata></shape>

o_builder_pattern_interaction

在上面的SAX示例中,expat实现了一个XML解析器,当它解析到起始TAG时就调用BuilderXML_StartElementHandler函数,解析到一个结束TAG时就调用BuilderXML_EndElementHandler函数,解析到其它元素时调用Builder其它相应的函数,直到解析完成为止。注意这些函数是由ConcreteBuilder实现的。

下面我们以一个歌词解析器为例进行更详细的讲解:

1. 歌词(lrc)文件介绍:

歌词(lrc)文件是一种文本文件,它用来描述歌曲的歌词。在歌词(lrc)文件的帮助下,播放器可以同步显示歌词。歌词(lrc)文件的格式很简单,它由时间标签、ID标签和歌词组成。

时间标签,如:[02:42.91][00:58.78]

ID标签,如:[ar:Whitney Houston]

歌词,如:I don’t really need to look very much further

下面是一个完整的歌词文件:

[al:]

[ar:Whitney Houston]

[ti:i have nothing(一无所有)]

I have nothing

演唱:Whitney Houston

专辑:

[00:20.95]Share my life, take me for what I am

[00:30.47]Cause I’ll never change all my colours for you

[00:40.16]Take my love, I’ll never ask for too much

[00:49.09]Just all that you are and everything that you do

[02:42.91][00:58.78]I don’t really need to look very much further

[02:47.74][01:03.69]I don’t want to have to go where you don’t follow

[02:52.37][01:08.25]I won’t hold it back again, this passion inside

[02:56.62][01:12.40]Can’t run from myself

[02:59.05][01:15.18]There’s nowhere to hide

[03:03.02](Your love I’ll remember forever)

[03:39.61][03:09.02][01:19.98]Don’t make me close one more door

[03:46.85][03:13.73][01:24.92]I don’t wanna hurt anymore

[03:51.05][03:17.90][01:29.04]Stay in my arms if you dare

[03:56.01][03:22.98][01:34.29]Or must I imagine you there

[04:09.89][04:06.04][04:00.35][03:27.61][01:38.77]Don’t walk away from me...

[04:15.73][03:32.56][01:43.95]I have nothing, nothing, nothing

[04:21.27][01:49.16]If I don’t have you, you

[01:59.65]you ,you, you

[04:30.64]If I don’t have you

[02:05.71]You see through, right to the heart of me

[02:14.49]You break down my walls with the strength of you love

[02:24.11]I never knew love like I’ve known it with you

[02:32.69]Will a memory survive, one I can hold on to

[04:36.33]Oh~~

2. Builder的接口定义:

struct _LrcBuilder;

typedef struct _LrcBuilder LrcBuilder;

typedef LRC_RESULT (*LrcBuilderBegin)(LrcBuilder* thiz, const char* buffer);

typedef LRC_RESULT (*LrcBuilderOnIDTag)(LrcBuilder* thiz, const char* key, size_t key_length,

const char* value, size_t value_length);

typedef LRC_RESULT (*LrcBuilderOnTimeTag)(LrcBuilder* thiz, size_t start_time);

typedef LRC_RESULT (*LrcBuilderOnLrc)(LrcBuilder* thiz, const char* lrc, size_t lrc_length);

typedef LRC_RESULT (*LrcBuilderEnd)(LrcBuilder* thiz);

typedef LRC_RESULT (*LrcBuilderDestroy)(LrcBuilder* thiz);

struct _LrcBuilder

{

LrcBuilderBegin on_begin;

LrcBuilderOnIDTag on_id_tag;

LrcBuilderOnTimeTag on_time_tag;

LrcBuilderOnLrc on_lrc;

LrcBuilderEnd on_end;

LrcBuilderDestroy destroy;

char priv[1];

};

l on_begin: 在解析之前调用,以便builder做些初始化工作。

l on_id_tag:解析到ID标签时调用。

l on_time_tag:解析到时间标签时调用。

l on_lrc:解析到歌词时调用。

l on_end:解析完成时调用。

l destroy:销毁builder时调用。

3. 两个builder的实现。

我们实现了两个builder

l dumpbuilder: 直接把解析的内容打印到屏幕上。它的功能有两个:一是用于调试目的,可以查看解析器工作是否正常。二是美化lrc文件,把排版比较乱的lrc文件,以正规的格式输出。它的主要代码如下:

static LRC_RESULT lrc_dump_builder_on_time_tag(LrcBuilder* thiz, size_t start_time)

{

int min = start_time / 6000;

int sec = start_time % 6000;

int per_sec = sec % 100;

sec = sec / 100;

LrcDumpBuilder* data = (LrcDumpBuilder*)thiz->priv;

fprintf(data->fp, "[%d:%02d.%02d]", min, sec, per_sec);

return LRC_RESULT_OK;

}

static LRC_RESULT lrc_dump_builder_on_lrc(LrcBuilder* thiz, const char* lrc, size_t lrc_length)

{

LrcDumpBuilder* data = (LrcDumpBuilder*)thiz->priv;

fwrite(lrc, lrc_length, 1, data->fp);

fprintf(data->fp, "/n");

return LRC_RESULT_OK;

}

LrcBuilder* lrc_dump_builder_new(FILE* fp)

{

LrcDumpBuilder* data = NULL;

LrcBuilder* thiz = (LrcBuilder*)calloc(sizeof(LrcBuilder) + sizeof(LrcDumpBuilder), 1);

if(thiz != NULL)

{

thiz->on_begin = lrc_dump_builder_on_begin;

thiz->on_id_tag = lrc_dump_builder_on_id_tag;

thiz->on_time_tag = lrc_dump_builder_on_time_tag;

thiz->on_lrc = lrc_dump_builder_on_lrc;

thiz->on_end = lrc_dump_builder_on_end;

thiz->destroy = lrc_dump_builder_destroy;

data = (LrcDumpBuilder*)thiz->priv;

data->fp = fp != NULL ? fp : stdout;

}

return thiz;

}

defaultbuilder: 是默认的builder,它负责把lrc文件构建成内存中的结构,以便可以方便查询。它的主要代码如下:

static LRC_RESULT lrc_default_builder_on_time_tag(LrcBuilder* thiz, size_t start_time)

{

LrcDefaultBuilder* data = NULL;

LRC_ASSERT(thiz != NULL);

if(thiz != NULL)

{

LrcTimeTag* time_tag = NULL;

LrcListIter iter = {0};

data = (LrcDefaultBuilder*)thiz->priv;

time_tag = lrc_time_tag_new(data->time_tag_pool, start_time, NULL);

iter = lrc_list_first(data->temp_time_tags);

lrc_list_insert(&iter, time_tag, 0);

}

return LRC_RESULT_OK;

}

static LRC_RESULT lrc_default_builder_on_lrc(LrcBuilder* thiz, const char* lrc, size_t lrc_length)

{

LrcDefaultBuilder* data = NULL;

LRC_ASSERT(thiz != NULL && lrc != NULL);

if(thiz != NULL && lrc != NULL)

{

char* new_lrc = NULL;

LrcListIter iter = {0};

LrcTimeTag* time_tag = NULL;

data = (LrcDefaultBuilder*)thiz->priv;

iter = lrc_list_first(data->temp_time_tags);

new_lrc = lrc_strdup(data, lrc, lrc_length);

while(!lrc_list_iter_is_null(&iter))

{

time_tag = (LrcTimeTag*)lrc_list_iter_data(&iter);

lrc_time_tag_set_lrc(time_tag, new_lrc);

lrc_tree_add_time_tag(data->tree, time_tag);

iter = lrc_list_iter_next(&iter);

}

}

lrc_list_reset(data->temp_time_tags);

return LRC_RESULT_OK;

}

LrcBuilder* lrc_default_builder_new(void)

{

LrcDefaultBuilder* data = NULL;

LrcBuilder* thiz = (LrcBuilder*)calloc(sizeof(LrcBuilder) + sizeof(LrcDefaultBuilder), 1);

if(thiz != NULL)

{

thiz->on_begin = lrc_default_builder_on_begin;

thiz->on_id_tag = lrc_default_builder_on_id_tag;

thiz->on_time_tag = lrc_default_builder_on_time_tag;

thiz->on_lrc = lrc_default_builder_on_lrc;

thiz->on_end = lrc_default_builder_on_end;

thiz->destroy = lrc_default_builder_destroy;

data = (LrcDefaultBuilder*)thiz->priv;

}

return thiz;

}

4. Product

所谓Product就是ConcreteBuilder产生的结果,不同的ConcreteBuilder所产生的Product是不相同的。

在我们的例子中,dumpbuilder产生的Product是一段文本。而defaultbuilder产生的Product是一个数据结构。如下:

struct _LrcIdTag

{

const char* key;

const char* value;

};

struct _LrcTimeTag

{

size_t start_time;

size_t pause_time;

size_t repeat_times;

const char* lrc;

};

struct _LrcTree

{

LrcList* id_tags;

LrcList* time_tags;

};

5. 解析器(Director)的工作过程。

解析器的任务就是解析出lrc最基本的元素:时间标签、ID标签和歌词,然后调用builder相应的函数。

static LRC_RESULT lrc_parser_parse_tag(LrcParser* thiz)

{

const char* p = thiz->p;

skip_space(p);

if(*p >= '0' && *p <= '9')

{

lrc_parser_parse_time_tag(thiz);

}

else

{

lrc_parser_parse_id_tag(thiz);

}

return LRC_RESULT_OK;

}

static LRC_RESULT lrc_parser_parse_id_tag(LrcParser* thiz)

{

const char* key = NULL;

const char* value = NULL;

size_t key_length = 0;

size_t value_length = 0;

if(lrc_parser_parse_pair(thiz, &key, &key_length, &value, &value_length) == LRC_RESULT_OK)

{

if(thiz->builder->on_id_tag != NULL)

{

thiz->builder->on_id_tag(thiz->builder, key, key_length, value, value_length);

}

}

return LRC_RESULT_OK;

}

static LRC_RESULT lrc_parser_parse_time_tag(LrcParser* thiz)

{

float seconds = 0;

char number[32] = {0};

size_t start_time = 0;

const char* key = NULL;

const char* value = NULL;

size_t key_length = 0;

size_t value_length = 0;

if(lrc_parser_parse_pair(thiz, &key, &key_length, &value, &value_length) == LRC_RESULT_OK)

{

key_length = key_length < 20 ? key_length : 20;

strncpy(number, key, key_length);

start_time = atoi(number) * 60 * 100;

value_length = value_length < 20 ? value_length : 20;

strncpy(number, value, value_length);

sscanf(number, "%f", &seconds);

start_time += seconds * 100;

if(thiz->builder->on_time_tag != NULL)

{

thiz->builder->on_time_tag(thiz->builder, start_time);

}

}

return LRC_RESULT_OK;

}

static LRC_RESULT lrc_parser_parse(LrcParser* thiz)

{

LRC_RESULT ret = LRC_RESULT_OK;

const char* p = thiz->p;

if(thiz->builder->on_begin != NULL)

{

thiz->builder->on_begin(thiz->builder, p);

}

while(*p != '/0')

{

if(*p == '[')

{

thiz->p = ++p;

skip_space(thiz->p);

lrc_parser_parse_tag(thiz);

}

else if(*p == '/n' || *p == '/r')

{

skip_to_next_line(thiz->p);

}

else if(*p == ' ' || *p == '/t')

{

skip_space(thiz->p);

}

else if(*p != ']')

{

thiz->p = p;

lrc_parser_parse_lrc(thiz);

}

else

{

++thiz->p;

}

p = thiz->p;

}

if(thiz->builder->on_end != NULL)

{

thiz->builder->on_end(thiz->builder);

}

return ret;

}

6. 调用者(client)

有了Parser和相应的Builder,调用者需要把它们组合起来,这个过程很简单。在解析完成时,调用者还希望从Builder取出Product,以便后面使用。如:

LrcParser* lrc_parser_new_from_file(const char* filename)

{

LrcParser* thiz = NULL;

char* buffer = read_file(filename);

if(buffer != NULL)

{

thiz = lrc_parser_new(buffer);

if(thiz != NULL)

{

thiz->owner_buffer = 1;

}

}

return thiz;

}

static Lrc* lrc_parse(LrcParser* parser)

{

Lrc* thiz = NULL;

if(parser != NULL)

{

LrcBuilder* builder = (LrcBuilder*)lrc_default_builder_new();

lrc_parser_run(parser, builder);

thiz = (Lrc*)lrc_default_builder_get_tree(builder);

builder->destroy(builder);

lrc_parser_destroy(parser);

}

return thiz;

}

附:

lrc解析器完整的代码可以到这里下载。

~~end~~

分享到:
评论

相关推荐

    复旦nois教材01.rar

    第二章 SOPC Builder开发环境......................................................................................................8 2.1 创建Quartus II工程..................................................

    Visual C++音频/视频处理技术及工程实践 (分卷1)

    本书共16章,分为4篇,详细讲解了使用各种软件和平台进行音、视频多媒体编程的技术,以案例为对象展示实现过程、分析技术难点。主要内容包括软件Visual C++2005的开发技术、DirectSound开发音频、DirectShow/VFW开发...

    Visual C++音频视频处理技术及工程实践(含源码2/2)

    本书共16章,分为4篇,详细讲解了使用各种软件和平台进行音、视频多媒体编程的技术,以案例为对象展示实现过程、分析技术难点。主要内容包括软件Visual C++2005的开发技术、DirectSound开发音频、DirectShow/VFW开发...

    Visual C++音频视频处理技术及工程实践地址

    本书共16章,分为4篇,详细讲解了使用各种软件和平台进行音、视频多媒体编程的技术,以案例为对象展示实现过程、分析技术难点。主要内容包括软件Visual C++2005的开发技术、DirectSound开发音频、DirectShow/VFW开发...

    Visual C++音频视频处理技术及工程实践(分卷0)

    本书共16章,分为4篇,详细讲解了使用各种软件和平台进行音、视频多媒体编程的技术,以案例为对象展示实现过程、分析技术难点。主要内容包括软件Visual C++2005的开发技术、DirectSound开发音频、DirectShow/VFW开发...

    Visual C++ 音频/视频 处理技术及工程实践(分卷3)

    本书共16章,分为4篇,详细讲解了使用各种软件和平台进行音、视频多媒体编程的技术,以案例为对象展示实现过程、分析技术难点。主要内容包括软件Visual C++2005的开发技术、DirectSound开发音频、DirectShow/VFW开发...

    Visual C++音频视频处理技术及工程实践(分卷9)

    本书共16章,分为4篇,详细讲解了使用各种软件和平台进行音、视频多媒体编程的技术,以案例为对象展示实现过程、分析技术难点。主要内容包括软件Visual C++2005的开发技术、DirectSound开发音频、DirectShow/VFW开发...

    Visual C++音频/视频处理技术及工程实践(分卷2)

    本书共16章,分为4篇,详细讲解了使用各种软件和平台进行音、视频多媒体编程的技术,以案例为对象展示实现过程、分析技术难点。主要内容包括软件Visual C++2005的开发技术、DirectSound开发音频、DirectShow/VFW开发...

    Visual C++音频视频处理技术及工程实践(分卷7)

    本书共16章,分为4篇,详细讲解了使用各种软件和平台进行音、视频多媒体编程的技术,以案例为对象展示实现过程、分析技术难点。主要内容包括软件Visual C++2005的开发技术、DirectSound开发音频、DirectShow/VFW开发...

    Visual C++音频视频处理技术及工程实践(分卷6)

    本书共16章,分为4篇,详细讲解了使用各种软件和平台进行音、视频多媒体编程的技术,以案例为对象展示实现过程、分析技术难点。主要内容包括软件Visual C++2005的开发技术、DirectSound开发音频、DirectShow/VFW开发...

    数据结构(C++)有关练习题

    在计算机科学发展过程中,早期数据结构教材大都采用PASCAL语言为描述工具,后来出现了采用C语言为描述工具的教材版本、至今又出现了采用C++语言为描述工具的多种教材版本。本教实验指导书是为已经学习过C++语言的...

Global site tag (gtag.js) - Google Analytics