作者联系方式:854290197@qq.com
契约式设计的六大原则
- 原则一:区分命令和查询。查询返回一个结果,但不改变对象的可见性质。命令改变对象的状态,但不返回结果。(应当是不一定返回结果)
- 原则二: 将简单查询同组合查询分开。组合查询可以用简单查询来定义。
- 原则三: 针对每个组合查询,设定一个后验条件,使用一个或多个简单查询的结果来定义它。这样我们只要知道简单查询的值,也就能知道组合查询的值。
- 原则四:对于每个命令都撰写一个后验条件,规定每个简单查询的值。结合“用简单查询定义组合查询”的原则,我们已经能够知道每个命令的全部可视效果。
- 原则五:对于每个查询和命令,采用一个合适的先验条件。先验条件限定了客户调用查询和命令的时机。
- 原则六:撰写不变式来定义对象的恒定特性。类是某种抽象的体现,应当将注意力集中在最重要的属性上,以帮助读者建立关于类抽象的正确概念模型。
程序设计
根据以上六大原则设计解析URL的程序。我们知道URL由服务类型、主机名、路径及文件名三大基本部分组成,可能还包含有参数、锚、端口号等。(这里我们用C语言编写程序,C语言同样可以实现面向对象的编程)
首先我们要设计好一个类,类包含成员和方法。根据原则一的要求,我们要在方法中区分好命令和查询。这个应该直接在命名上就体现出来,比如查询应该使用getXXX等,一眼看到就知道这个是查询,不改变对象状态,哪个是命令,会改变对象状态。这个分类除了让我们能一眼就看出operation的特点之外,还是整个DbC在Operation组合的正则性的基础。如果划分错误,则可能破坏Operation组合的正则性。以下是我设计的类:
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的字符串,解析的过程中有多种状态,遇到某种特定的字符就会切换到相应状态。包含以下几个状态:
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_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在组合查询中设置了后验条件,用来检验需查询的数据是否存在。
根据原则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对象的属性自始至终都是满足某种相应的状态。在命令中,切换不同的状态,就会设置不同的属性。
感谢阅读!希望会有更多有用的内容分享给大家。