程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> Builder模式實例分析(C語言版)

Builder模式實例分析(C語言版)

編輯:關於C語言
 

設計模式、設計模式還是設計模式,設計模式已經被許多高手講過了無數遍了。本來我無意再去重復被人重復過無數遍的工作,但按照我們的培訓計劃,現在該講設計模式了,作為培訓計劃的制定者,我不能不貢獻一點力量,所以最終決定寫幾篇關於設計模式的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(如),結束tag(如)和起始/結束tag(如),SAX解析器(Parser)會解析出這些基本的元素,但並不把這些元素構建成一棵DOM樹,而是把這些基本元素交給創建者Builder處理。至於Builder拿這些元素去創建什麼,SAX解析器是不管的。不同的Builder會做不同的事情,有的Builder可能會用這些基本元素構建成一棵DOM樹,有的Builder可能只是為了取出某個tag的屬性,而有的Builder可能會把OpenOffice文檔轉換成PDF文檔。總之,Builder可以實現不同的功能,但解析器始終是被重用的。

從SAX例子中,我們可以看出:采用Builder模式,可以重用解析器部分的代碼,把變化的部分隔離到Builder裡,這是從代碼重用和隔離變化的角度考慮的。另外一方面,分離解析和創建兩個過程,實際上也簡化了問題的復雜度,解析器部分只負責解析,實現起來更加容易。而且創建者Builder更了解自己的目標,會做得更專業。

Builder的模式的結構:

Builder模式實例分析(C語言版) - 下雪了 - 誰的博客

在上面的SAX例子中,Director就是解析器(Parser),而Builder是SAX要求提供的一個抽象接口,ConcreteBuilder是Builder接口的具體實現。比如某個ConcreteBuilder的功能可能是創建一棵DOM樹,另外一個ConcreteBuilder的功能可能是把OpenOffice文檔轉換為PDF文檔,總之,Director不關心Builder的具體實現。

Builder的模式的交互過程:

Builder模式實例分析(C語言版) - 下雪了 - 誰的博客

在上面的SAX示例中,expat實現了一個XML解析器,當它解析到起始TAG時就調用Builder的XML_StartElementHandler函數,解析到一個結束TAG時就調用Builder的XML_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];};
o on_begin: 在解析之前調用,以便builder做些初始化工作。

o on_id_tag:解析到ID標簽時調用。

o on_time_tag:解析到時間標簽時調用。

o on_lrc:解析到歌詞時調用。

o on_end:解析完成時調用。

o destroy:銷毀builder時調用。

3. 兩個builder的實現。

我們實現了兩個builder:

o 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;}
 

 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved