根據契約式設計原則編寫解析URL的程序

做者聯繫方式:854290197@qq.com編程

契約式設計的六大原則

  1. 原則一:區分命令和查詢。查詢返回一個結果,但不改變對象的可見性質。命令改變對象的狀態,但不返回結果。(應當是不必定返回結果)
  2. 原則二: 將簡單查詢同組合查詢分開。組合查詢能夠用簡單查詢來定義。
  3. 原則三: 針對每一個組合查詢,設定一個後驗條件,使用一個或多個簡單查詢的結果來定義它。這樣咱們只要知道簡單查詢的值,也就能知道組合查詢的值。
  4. 原則四:對於每一個命令都撰寫一個後驗條件,規定每一個簡單查詢的值。結合「用簡單查詢定義組合查詢」的原則,咱們已經可以知道每一個命令的所有可視效果。
  5. 原則五:對於每一個查詢和命令,採用一個合適的先驗條件。先驗條件限定了客戶調用查詢和命令的時機。
  6. 原則六:撰寫不變式來定義對象的恆定特性。類是某種抽象的體現,應當將注意力集中在最重要的屬性上,以幫助讀者創建關於類抽象的正確概念模型。

程序設計

根據以上六大原則設計解析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

相關文章
相關標籤/搜索