契约式设计的定义
契约式设计(Design by Contract (DbC)),这种设计方式和商业契约的情况类似。契约作用于两方,每一方都会完成一些任务,从而促成契约的达成,但同时,每一方也会接受一些义务,作为制定契约的前提,有任意一方无视了必尽义的义务,则契约失败。
契约式设计的原则
·区分命令和查询。查询返回一个结果,但不改变对象的可见性质。命令改变对象的状态,但不一定返回结果。
· 将基本查询和派生查询分开。派生查询可以用基本查询来定义。
· 设定一个后验条件,使用一个或多个基本查询的结果来定义每个派生查询。这样我们只要知道基本查询的值,也就能知道派生查询的值。
例子
上述原则多用于对类进行设计,命令选择使用set函数设置,而查询选择直接通过对象获取。
通过URL类的接口函数作为例子:
typedef int bool;
#define true 1
#define false 0
/* 包含url各个部分的结构体。 */
typedef struct _url_t {
char* protocol; /* 协议 */
char* domain; /* 域名 */
int port; /* 端口号 */
char* vdir; /* 虚拟目录 */
char* filename; /* 文件名 */
int para_num; /* 参数数量 */
char** para; /* 参数 */
char* anchor; /* 锚 */
} url_t;
/**
* @method url_create
* 创建url结构体。(需要销毁url结构体请调用url_destroy函数)
*
* @return {url_t*} 返回url结构体指针(若无内存创建,返回值为空)。
*/
url_t* url_create(void);
/**
* @method url_parse
* 将url字符串解析为各个部分。
* @param {const char*} str_url 需解析的url字符串。
* @param {url_t*} url 已创建的url指针。
*
* @return {bool} 若返回true,则url字符串解析成功,否则为失败。
*/
bool url_parse(const char* str_url, url_t* url);
/**
* @method url_set_protocol
* 设置url结构体的protocol(协议)成员。
* @param {url_t*} url 已创建的url指针。
* @param {const char*} protocol 协议。
*
* @return {bool} 若返回true,则url的protocol成员设置成功,否则为失败。
*/
bool url_set_protocol(url_t* url, const char* protocol);
/**
* @method url_set_domain
* 设置url结构体的domain(域名)成员。
* @param {url_t*} url 已创建的url指针。
* @param {const char*} domain 域名。
*
* @return {bool} 若返回true,则url的domain成员设置成功,否则为失败。
*/
bool url_set_domain(url_t* url, const char* domain);
/**
* @method url_set_port
* 设置url结构体的port(端口号)成员。
* @param {url_t*} url 已创建的url指针。
* @param {int} port 端口号。
*
* @return {bool} 若返回true,则url的port成员设置成功,否则为失败。
*/
bool url_set_port(url_t* url, int port);
/**
* @method url_set_vdir
* 设置url结构体的vdir(虚拟目录)成员。
* @param {url_t*} url 已创建的url指针。
* @param {const char*} vdir 虚拟目录。
*
* @return {bool} 若返回true,则url的vdir成员设置成功,否则为失败。
*/
bool url_set_vdir(url_t* url, const char* vdir);
/**
* @method url_set_filename
* 设置url结构体的filename(文件名)成员。
* @param {url_t*} url 已创建的url指针。
* @param {const char*} filename 文件名。
*
* @return {bool} 若返回true,则url的filename成员设置成功,否则为失败。
*/
bool url_set_filename(url_t* url, const char* filename);
/**
* @method url_set_anchor
* 设置url结构体的anchor(锚)成员。
* @param {url_t*} url 已创建的url指针。
* @param {const char*} anchor 锚。
*
* @return {bool} 若返回true,则url的anchor成员设置成功,否则为失败。
*/
bool url_set_anchor(url_t* url, const char* anchor);
/**
* @method url_set_para
* 设置url结构体的para(参数)成员。
* @param {url_t*} url 已创建的url指针。
* @param {const char*} para 参数。
* @param {int16_t} para_index 参数位置(para_index等于0为第一个参数,若para_index大于等于参数的个数,则会新增一个参数)。
*
* @return {bool} 若返回true,则url的para成员设置成功,否则为失败。
*/
bool url_set_para(url_t* url, const char* para, int16_t para_index);
/**
* @method url_destroy
* 将url结构体销毁。
* @param {url_t*} url 已创建的url指针。
*/
void url_destroy(url_t* url);
契约式设计的约束条件
·前置条件(precondition):在执行函数体前,对参数进行检查,当参数无误后,再执行函数体。
· 后置条件(postcondition):在函数体执行后,对函数体返回的结果进行检查。
· 类不变项(class invariant):在函数体执行前后,对不会发生改变的条件进行检查。
例子
因为用在实际程序中会降低效率,所以上述约束条件多用于单元测试。
通过URL类的分析函数测试作为例子:
#define TEST_URL "https://www.kun.com:443/kun/dir/KUN?1&2#name"
TEST(UrlParse, UrlParse) {
char* str = TEST_URL;
url_t* url = url_create();
/* 先验条件 */
ASSERT_NE(nullptr, url); /* 类不变项 */
/***********/
bool ret = url_parse(str, url); /* 函数体 */
/* 后验条件 */
ASSERT_EQ(true, ret);
ASSERT_NE(nullptr, url); /* 类不变项 */
/***********/
ASSERT_STREQ("https", url->protocol);
ASSERT_STREQ("www.kun.com", url->domain);
ASSERT_EQ(443, url->port);
ASSERT_STREQ("kun/dir", url->vdir);
ASSERT_STREQ("KUN", url->filename);
ASSERT_EQ(2, url->para_num);
ASSERT_STREQ("1", url->para[0]);
ASSERT_STREQ("2", url->para[1]);
ASSERT_STREQ("name", url->anchor);
url_destroy(url);
}
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理