HITSC_6_Abstract Data Type (ADT)

Ch1ldKing發表於2024-05-28

Abstraction and User-Defined Types

📕資料抽象:指由一組操作所刻畫的資料型別
傳統的型別關注資料的具體表示,而抽象型別強調“作用於資料上的操作”,無需關注資料如何儲存,而是設計並使用操作
ADT是由操作定義的,與其內部如何實現無關!
比如,定義一個抽象的Bool類,Bool可以由很多的東西實現,但它的操作決定了它的性質
image.png

型別與操作

型別

可變型別和不可變型別
區別:可變型別提供了可改變內部資料的值的操作,而不變型別只能構造新的物件

操作

構造器

初始化物件的狀態,包括底層資料結構等,同時驗證不變性,有以下說法:

  1. 可以不帶引數,並創造一個空物件
  2. 可以不帶引數,並創造一個有初始預設值的物件
  3. 可以帶引數,創造一個初始化物件

變值器

如果返回值為void,則必然意味著它改變了物件的某些內部狀態
也有可能非空

ADT舉例

image.png

設計ADT

設計好的ADT,靠“經驗法則”,提供一組操作,設計其行為規約 spec

  1. 設計簡潔、一致的操作
  2. 足以支援client對資料所做的所有操作需要,且用操作滿足client需要的難度要低
  3. 要麼抽象、要麼具體,不要混合---要麼針對抽象設計,要麼針對具體應用的設計

表示獨立性

表示獨立性:client使用ADT時無需考慮其內部如何實現,ADT內部表示的變化不應影響外部spec和客戶端
這意味著當我們對程式碼進行最佳化時,我們無需通知客戶端也無需修改客戶端程式碼,因為最佳化後仍然滿足spec。這就是RI的作用
🌰例子:

違反RI
/**
 * Represents a family that lives in a household together.
 * A family always has at least one person in it.
 * Families are mutable.
 */
class Family {
    public List<Person> people;
    public List<Person> getMembers() {
        return people;
    }
}

void client1(Family f) {
    Person baby = f.people.get(f.people.size() - 1); // 直接訪問內部表示,違反封裝
    // ...
}

問題:直接暴露了people的內部,並且沒有封裝好。查詢方法依賴於具體內部有多少個元素這類的實現細節

改進RI
/**
 * Represents a family that lives in a household together.
 * A family always has at least one person in it.
 * Families are mutable.
 */
class Family {
    // 使用Set代替List,以避免重複元素
    public Set<Person> people;

    /**
     * @return a list containing all the members of the family, with no duplicates.
     */
    public List<Person> getMembers() {
        return new ArrayList<>(people);
    }
}

void client3(Family f) {
    // 透過getMembers方法獲取成員列表,而不是直接訪問內部表示
    Person anybody = f.getMembers().get(0); 
    // ...
}
  1. 採用Set,防止成員重複,同時改進了List的實現細節問題
  2. 透過getmembers來獲取成員列表,而不是直接暴露內部people,防止修改
  3. getMembers不直接返回people,而是複製一個列表,防止表示暴露

測試一個ADT

  1. 用observers測試creators、producers、mutators
  2. 呼叫creators、producers、mutators等產生或修改結果來測試 observers
    這麼做有風險,有可能依賴的其他方法有錯誤導致測試結果失效
    🌰例子:測試策略:測試觀察者時要呼叫所有的構造器和生產者
//測試構造器
valueOf():
	true, false
	string = produced by valueOf()
//測試觀察者
length():
	string len = 0, 1, n
	string = produced by valueOf(), produced by substring()
//測試觀察者
charAt():
	string len = 1, n
	i = 0, 1, len - 1
	string = produced by valueOf(), produced by substring()
//測試生產者
substring():
	string len = 0, 1, n
	start = 0, middle, len
	end = 0, middle, len
	end-start = 0, n
	string = produced by valueOf(), produced by substring()

在測試時,其實是組合起來測試的,能保證所有方法之間的互動,同時覆蓋全面,如下,每種測試都採用了多種混合的方式image.png

不變數Invariants

一個任何時候都不發生改變的量,用來保持程式的正確性,發現錯誤

防止不變數被修改

不能產生表示洩露
用private 和final 進行修飾來實現一些變數不會被外部獲取或修改
🌰例子:比如這個,當設定timestamp為final時,image.png
d也指向了t的timestamp,則對tweet的timestamp產生了修改,導致表示洩露,修改了不變數,導致Tweet不能保證它是不變數image.png

RI 和 AF

image.png

RI

表示不變數RI:某個具體的“表示”是否是“合法的”
也就是R的一個子集,這裡面都是合法的輸入,是一些限定條件
❓為什麼是表示不變數:在方法執行完後,要仍然保持住所設定好的RI

AF

抽象函式:R和A之間對映關係的函式,即如何去解釋R中的每一個值為A中的每一個值
也就是對映到客戶端那裡的值,需要具體解釋是如何對映的

checkRep()

透過一個方法checkRep()來保證任何時候都是不變數,所有可能改變rep的地方都要檢查

相關文章