根据契约式设计原则编写解析URL的程序

作者联系方式:854290197@qq.com

契约式设计的六大原则

  1. 原则一:区分命令和查询。查询返回一个结果,但不改变对象的可见性质。命令改变对象的状态,但不返回结果。(应当是不一定返回结果)
  2. 原则二: 将简单查询同组合查询分开。组合查询可以用简单查询来定义。
  3. 原则三: 针对每个组合查询,设定一个后验条件,使用一个或多个简单查询的结果来定义它。这样我们只要知道简单查询的值,也就能知道组合查询的值。
  4. 原则四:对于每个命令都撰写一个后验条件,规定每个简单查询的值。结合“用简单查询定义组合查询”的原则,我们已经能够知道每个命令的全部可视效果。
  5. 原则五:对于每个查询和命令,采用一个合适的先验条件。先验条件限定了客户调用查询和命令的时机。
  6. 原则六:撰写不变式来定义对象的恒定特性。类是某种抽象的体现,应当将注意力集中在最重要的属性上,以帮助读者建立关于类抽象的正确概念模型。

程序设计

根据以上六大原则设计解析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对象的属性自始至终都是满足某种相应的状态。在命令中,切换不同的状态,就会设置不同的属性。

感谢阅读!希望会有更多有用的内容分享给大家。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值