一個子產品有兩部分組成:接口和實作。接口指明子產品要做什麼,它聲明了使用該子產品的代碼可用的辨別符、類型和例程,實作指明子產品是如何完成其接口聲明的目标的,一個給定的子產品通常隻有一個接口,但是可能會有許多種實作能夠提供接口所指定的功能。每個實作可能使用不同的算法和資料結構,但是它們都必須符合接口所給出的使用說明。客戶調用程式是使用某個子產品的一段代碼,客戶調用程式導入接口,而實作導出接口。由于多個客戶調用程式是共享接口和實作的,是以使用實作的目标代碼避免了不必要的代碼重複,同時也有助于避免錯誤,因為接口和實作隻需一次編寫和調試就可多次使用。
接口隻需要指明客戶調用程式可能使用的辨別符即可,應盡可能地隐藏一些無關的表示細節和算法,這樣客戶調用程式可以不必依賴于特定的實作細節。這種客戶調用程式和實作之間的依賴--耦合----可能會在實作改變時引起錯誤,當這種依賴性埋藏在一些關于實作隐藏的或是不明确的假設中時,這些錯誤可能很難修複,是以一個設計良好且描述精确的接口應該盡量減少耦合。
c語言對接口和實作的分離隻提供最基本的支援,但是簡單的約定能給接口/實作方法論帶來巨大的好處。在c中,接口在頭檔案聲明,頭檔案聲明了客戶調用程式可以使用的宏、類型、資料結構、變量以及例程。使用者使用c語言的預處理指令#include導入接口。
下面的例子說明了本篇文章的接口中所使用的一些約定、接口:
該接口的名字為arith,接口頭檔案也相應地命名為arith.h,接口的名字以字首的形式出現在接口的每個辨別符中。子產品名不僅提供了合适的字首,而且還有助于整理客戶調用程式代碼。
arith接口還提供了一些标準c函數庫中沒有但是很有用的函數,并為出發和取模提供了良好的定義,而标準c中并沒有給出這些操作的定義和隻提供基于實作的定義。
一個實作導出一個接口,它定義了必要的變量和函數以提供接口所規定的功能,在c語言中,一個實作是由一個或多個.c檔案提供的,一個實作必須提供其導出的接口所指定的功能。實作應包含接口的.h檔案,以保證它的定義和接口的聲明時一緻的。
arith_min和arith_max傳回其整型參數中的最小值和最大值:
arith_div傳回y除以x得到的商,arith_mod傳回相應的餘數。當x與y同号的時候,arith_div(x,y)等價于x/y,arith_mod(x,y)等價于x%y
當x與y的符号不同的時候,c的内嵌操作的傳回值就取決于具體的實作:
eg.如果-13/5=2,-13%5=-3,如果-13/5=-3,-13%5=2
标準庫函數總是向零取整,是以div(-13,2)=-2,arith_div和arith_mod的語義同樣定義好了:它們總是趨近數軸的左側取
整,是以arith_div(-13,5)=-3,arith_div(x,y)是不超過實數z的最大整數,其中z滿足z*y=x。
arith_mod(x,y)被定義為x-y*arith_div(x,y)。是以arith_mod(-13,5)=-13-5*(-3)=2
函數arith_ceiling和arith_floor遵循類似的約定,arith_ceiling(x,y)傳回不小于實數商x/y的最小整數
arith_floor(x,y)傳回不超過實數商x/y的最大整數
完整實作代碼如下:
抽象資料類型(abstract data type,adt)是一個定義了資料類型以及基于該類型值提供的各種操作的接口
一個進階類型是抽象的,因為接口隐藏了它的表示細節,以免客戶調用程式依賴這些細節。下面是一個抽象資料類型(adt)的規範化例子--堆棧,它定義了該類型以及五種操作:
包含相關頭檔案:
stack_t的内部是一個結構,該結構有個字段指向一個棧内指針的連結清單以及一個這些指針的計數:
stack_new配置設定并初始化一個新的t:
其中new是一個另一個接口中的一個配置設定宏指令。new(p)将配置設定該結構的一個執行個體,并将其指針賦給p,是以stack_new中使用它就可以配置設定一個新的stack_t
當count=0時,stack_empty傳回1,否則傳回0:
assert(stk)實作了可檢查的運作期錯誤,它禁止空指針傳給stack中的任何函數。
stack_push和stack_pop從stk->head所指向的連結清單的頭部添加或移出元素:
free是另一個接口中定義的釋放宏指令,它釋放指針參數所指向的空間,然後将參數設為空指針
《c語言接口與實作--建立可重用軟體的技術》