契約式設計的六大原則
- 原則一:區分命令和查詢。查詢返回一個結果,但不改變對象的可見性質。命令改變對象的狀態,但不返回結果。(應當是不必定返回結果)
- 原則二: 將簡單查詢同組合查詢分開。組合查詢能夠用簡單查詢來定義。
- 原則三: 針對每一個組合查詢,設定一個後驗條件,使用一個或多個簡單查詢的結果來定義它。這樣咱們只要知道簡單查詢的值,也就能知道組合查詢的值。
- 原則四:對於每一個命令都撰寫一個後驗條件,規定每一個簡單查詢的值。結合「用簡單查詢定義組合查詢」的原則,咱們已經可以知道每一個命令的所有可視效果。
- 原則五:對於每一個查詢和命令,採用一個合適的先驗條件。先驗條件限定了客戶調用查詢和命令的時機。
- 原則六:撰寫不變式來定義對象的恆定特性。類是某種抽象的體現,應當將注意力集中在最重要的屬性上,以幫助讀者創建關於類抽象的正確概念模型。
程序設計
根據以上六大原則設計解析URL的程序。咱們知道URL由服務類型、主機名、路徑及文件名三大基本部分組成,可能還包含有參數、錨、端口號等。(這裏咱們用C語言編寫程序,C語言一樣能夠實現面向對象的編程)函數
首先咱們要設計好一個類,類包含成員和方法。根據原則一的要求,咱們要在方法中區分好命令和查詢。這個應該直接在命名上就體現出來,好比查詢應該使用getXXX等,一眼看到就知道這個是查詢,不改變對象狀態,哪一個是命令,會改變對象狀態。這個分類除了讓咱們能一眼就看出operation的特色以外,仍是整個DbC在Operation組合的正則性的基礎。若是劃分錯誤,則可能破壞Operation組合的正則性。如下是我設計的類:post
typedef char* (*url_get_protocol_t)(url_t* url); typedef char* (*url_get_host_t)(url_t* url); typedef char* (*url_get_path_t)(url_t* url); typedef char* (*url_get_search_t)(url_t* url); typedef char* (*url_get_query_t)(url_t* url); typedef char* (*url_get_hash_t)(url_t* url); typedef char* (*url_get_protocol_host_t)(url_t* url); typedef char* (*url_get_protocol_host_post_t)(url_t* url); typedef uint32_t (*url_get_port_t)(url_t* url); typedef ret_t (*parse_url_t)(url_t* url); // url結構信息 typedef struct _url_t { /** * @property {char* } href * url字符串。 */ char* href; /** * @property {char* } protocol * 協議。 */ char* protocol; /** * @property {char* } host * 主機名 */ char* host; /** * @property {char* } path * 路徑 */ char* path; /** * @property {char* } search * 搜索參數 */ char* search; /** * @property {char* } hash * 錨 */ char* hash; /** * @property {char* } query * 查詢參數 */ char* query; /** * @property {uint32_t } port * 端口號 */ uint32_t port; /* 命令 */ parse_url_t parse_url; /* 簡單查詢成員的值 */ url_get_protocol_t url_get_protocol; url_get_host_t url_get_host; url_get_path_t url_get_path; url_get_search_t url_get_search; url_get_query_t url_get_query; url_get_port_t url_get_port; url_get_hash_t url_get_hash; /* 組合查詢成員的值 */ url_get_protocol_host_t get_protocol_host_t; url_get_protocol_host_post_t get_protocol_host_post_t; } url_t;
在該類中只有一個parse_url命令,用來獲取url結構體成員中各個成員的值。在命令中,我用了狀態機實現解析URL的字符串,解析的過程當中有多種狀態,遇到某種特定的字符就會切換到相應狀態。包含如下幾個狀態:ui
typedef enum _char_state_t { /** * @const STATE_START * 起始狀態。 */ STATE_START = 0, /** * @const STATE_PROTOCOL * 協議狀態。 */ STATE_PROTOCOL, /** * @const STATE_HOST * 主機狀態。 */ STATE_HOST, /** * @const STATE_POST * 端口狀態。 */ STATE_POST, /** * @const STATE_PATH * 路徑狀態。 */ STATE_PATH, /** * @const STATE_HASH * 錨狀態。 */ STATE_HASH, /** * @const STATE_PARA * 參數狀態。 */ STATE_PARA, /** * @const STATE_END * 結束狀態。 */ STATE_END } char_state_t;
根據原則4的要求,每個命令都須要有一個後驗條件,用來規定簡單查詢的值。當進入某種狀態,可是卻沒有獲得相應的值,這是不合理的,這就須要咱們在狀態結束的時候設置一個後驗條件,驗證是否獲得了相應的值。命令以下所示:url
/************* 狀態機解析URL各部分到url_t結構體 *************/ static ret_t url_parse(url_t* url) { return_value_if_fail(url != NULL, RET_FAIL); char_state_t STATE = STATE_START; char* ptr_start = url->href; char* ptr_now = url->href; while (*ptr_now) { switch (STATE) { case STATE_START: if (!IS_SPACE_SEPARATOR(*ptr_now)) { ptr_start = ptr_now; STATE = STATE_PROTOCOL; break; } break; case STATE_PROTOCOL: if (IS_PROTOCOL_SEPARATOR(ptr_now)) { copy_str_to_url_t(&url->protocol, ptr_start, ptr_now); /* 後驗條件 */ return_value_if_fail(strlen(url->protocol) !=0 ,RET_FAIL); ptr_now += 3; ptr_start = ptr_now; STATE = STATE_HOST; break; } break; case STATE_HOST: if (IS_PORT_SEPARATOR(*ptr_now) || IS_PATH_SEPARATOR(*ptr_now) || IS_PARA_SEPARATOR(*ptr_now) || IS_ANCHOR_SEPARATOR(*ptr_now)) { copy_str_to_url_t(&url->host, ptr_start, ptr_now); /* 後驗條件 */ return_value_if_fail(strlen(url->host) !=0 ,RET_FAIL); ptr_start = ptr_now + 1; STATE = switch_state(*ptr_now); break; } break; case STATE_POST: if (IS_PATH_SEPARATOR(*ptr_now) || IS_PARA_SEPARATOR(*ptr_now) || IS_ANCHOR_SEPARATOR(*ptr_now)) { char* port_temp = NULL; copy_str_to_url_t(&port_temp, ptr_start, ptr_now); url->port = tk_atoi(port_temp); return_value_if_fail(url->port > 0 ,RET_FAIL); ptr_start = ptr_now + 1; STATE = switch_state(*ptr_now); TKMEM_FREE(port_temp); break; } break; case STATE_PATH: if (IS_PARA_SEPARATOR(*ptr_now) || IS_ANCHOR_SEPARATOR(*ptr_now)) { copy_str_to_url_t(&url->path, ptr_start, ptr_now); /* 後驗條件 */ return_value_if_fail(url->path != NULL ,RET_FAIL); ptr_start = ptr_now + 1; STATE = switch_state(*ptr_now); break; } case STATE_PARA: if (IS_ANCHOR_SEPARATOR(*ptr_now)) { copy_str_to_url_t(&url->search, ptr_start - 1, ptr_now); /* 後驗條件 */ return_value_if_fail(url->search != NULL ,RET_FAIL); copy_str_to_url_t(&url->query, ptr_start, ptr_now); /* 後驗條件 */ return_value_if_fail(url->query != NULL ,RET_FAIL); ptr_start = ptr_now + 1; STATE = switch_state(*ptr_now); } break; default: break; } ptr_now++; } Processing_before_ending(STATE, url, ptr_start, ptr_now); STATE = STATE_END; return RET_OK; }
根據原則2的要求,將組合查詢和簡單查詢分開,組合查詢能夠用簡單查詢來定義:設計
/**************** 簡單查詢 *****************************/ static char* get_protocol(url_t* url) { return_value_if_fail(url != NULL, NULL); return url->protocol; } static char* get_host(url_t* url) { return_value_if_fail(url != NULL, NULL); return url->host; } static char* get_path(url_t* url) { return_value_if_fail(url != NULL, NULL); return url->path; } static char* get_search(url_t* url) { return_value_if_fail(url != NULL, NULL); return url->search; } static char* get_query(url_t* url) { return_value_if_fail(url != NULL, NULL); return url->query; } static char* get_hash(url_t* url) { return_value_if_fail(url != NULL, NULL); return url->hash; } static uint32_t get_port(url_t* url) { return_value_if_fail(url != NULL, RET_BAD_PARAMS); return url->port; } /**************** 簡單查詢 *****************************/ static char* get_protocol(url_t* url) { return_value_if_fail(url != NULL, NULL); return url->protocol; } static char* get_host(url_t* url) { return_value_if_fail(url != NULL, NULL); return url->host; } static char* get_path(url_t* url) { return_value_if_fail(url != NULL, NULL); return url->path; } static char* get_search(url_t* url) { return_value_if_fail(url != NULL, NULL); return url->search; } static char* get_query(url_t* url) { return_value_if_fail(url != NULL, NULL); return url->query; } static char* get_hash(url_t* url) { return_value_if_fail(url != NULL, NULL); return url->hash; } static uint32_t get_port(url_t* url) { return_value_if_fail(url != NULL, RET_BAD_PARAMS); return url->port; } /*********************** 組合查詢 *************/ static char* get_protocol_host(url_t* url) { return_value_if_fail(url != NULL, NULL); uint32_t size = 0; char* temp = NULL; if (strlen(get_host(url)) != 0) { /* 後驗條件 */ size = strlen(get_protocol(url)) + strlen(get_host(url)) + strlen("://") + 1; temp = TKMEM_ALLOC(size); return_value_if_fail(temp != NULL, NULL); tk_snprintf(temp, size, "%s://%s", get_protocol(url), get_host(url)); } return temp; } static char* get_protocol_host_post(url_t* url) { return_value_if_fail(url != NULL, NULL); uint32_t size = 0; char* temp = NULL; char buff[20] = {0}; if (strlen(get_host(url)) != 0) { /* 後驗條件 */ size = strlen(get_protocol(url)) + strlen(get_host(url)) + strlen("://") + 1; temp = TKMEM_ALLOC(size); return_value_if_fail(temp != NULL, NULL); tk_snprintf(temp, size, "%s://%s", get_protocol(url), get_host(url)); if (url->port != 0) { /* 後驗條件 */ size = strlen(get_protocol(url)) + strlen(get_host(url)) + strlen("://") + 1 + strlen(tk_itoa(buff, sizeof(buff), get_port(url))) + 1; temp = TKMEM_REALLOCT(char, temp, size); return_value_if_fail(temp != NULL, NULL); tk_snprintf(temp, size, "%s:%d", temp, get_port(url)); } } return temp; }
程序中,組合查詢例如查詢服務類型和主機,可經過分別查詢服務類型和主機來實現。根據原則3在組合查詢中設置了後驗條件,用來檢驗需查詢的數據是否存在。code
根據原則5的要求在每一個查詢和命令中都設置了先驗條件,用來檢驗url對象是否已經建立。在程序中須要設計建立對象和銷燬對象的函數:對象
/************* 建立url結構體 *************/ url_t* url_create(const char* url_href) { return_value_if_fail(url_href != NULL, NULL); url_t* url = TKMEM_ALLOC(sizeof(url_t)); return_value_if_fail(url != NULL, NULL); url->href = TKMEM_ALLOC(strlen(url_href) + 1); tk_strcpy(url->href, url_href); url->protocol = TKMEM_CALLOC(1, sizeof(char)); url->host = TKMEM_CALLOC(1, sizeof(char)); url->path = TKMEM_CALLOC(1, sizeof(char)); url->query = TKMEM_CALLOC(1, sizeof(char)); url->search = TKMEM_CALLOC(1, sizeof(char)); url->hash = TKMEM_CALLOC(1, sizeof(char)); return_value_if_fail((NULL != url->protocol && NULL != url->host && NULL != url->path && NULL != url->query && NULL != url->search), (url_destory(url), NULL)); url->port = 0; url->url_get_protocol = get_protocol; url->url_get_host = get_host; url->url_get_path = get_path; url->url_get_search = get_search; url->url_get_query = get_query; url->url_get_hash = get_hash; url->url_get_port = get_port; url->get_protocol_host_t = get_protocol_host; url->get_protocol_host_post_t = get_protocol_host_post; url->parse_url = url_parse; return url; } /************* 銷燬url結構體 *************/ void url_destory(url_t* url) { return_if_fail(NULL != url); TKMEM_FREE(url->protocol); TKMEM_FREE(url->host); TKMEM_FREE(url->path); TKMEM_FREE(url->query); TKMEM_FREE(url->search); TKMEM_FREE(url->hash); TKMEM_FREE(url->href); url->port = 0; TKMEM_FREE(url); return; }
根據原則6對象的屬性自始至終都是知足某種相應的狀態。在命令中,切換不一樣的狀態,就會設置不一樣的屬性。字符串
感謝閱讀!但願會有更多有用的內容分享給你們。get