哈工大软件构造课程知识点总结(三)
系列文章目錄
哈工大軟件構造課程知識點總結(一)
哈工大軟件構造課程知識點總結(二)
哈工大軟件構造課程知識點總結(三)
哈工大軟件構造課程知識點總結(四)
哈工大軟件構造課程知識點總結(五)
哈工大軟件構造課程知識點總結(六)
文章目錄
- 系列文章目錄
- 簡介
- Chapter 5:Designing Specification
- 規約簡要介紹
- 行為等價性
- 規約的設計
- 規約的比較
- 規約畫圖
- 如何設計好的規約
- Chapter 6:Abstract Data Type
- 抽象數據類型相關概念
- 設計抽象數據類型
- 測試抽象數據類型
- 表示獨立性
- 表示不變性
- 抽象函數
- 表示不變性(RI)與抽象函數(AF)之間的關系
- 表示泄露
- 有益的可變性
簡介
此文章是2021春哈工大軟件構造課程Chapter 5、Chapter 6的知識點總結。
Chapter 5:Designing Specification
規約簡要介紹
“方法”是程序的積木,可以被獨立開發、測試、復用。使用“方法”的客戶端,無需了解內部的具體實現,這就是“抽象” 的思想。
一個完整的方法應包含規約和實現兩大部分,如下圖示例:
代碼本身就蘊含著“設計決策”(如使用final關鍵字說明此變量不可變),但這遠遠不夠。我們需要注釋形式的“設計決策”(規約)以供自己和他人閱讀。
規約(spec)給程序員和用戶雙方都確定了責任,調用時雙方都要遵守。
規約的作用:
- 隔離“變化”,無需通知客戶端——“防火墻”
- 解耦,客戶端不需了解具體實現
- 提高代碼效率
規約的內容:
- 輸入/輸出的數據類型
- 方法的功能和正確性
- 性能
只講“能做什么”,不講“如何實現”!
行為等價性
根據代碼的規約,站在客戶端視角看行為等價性。
例: 有以下兩個方法:
對于以下規約:
由于兩個函數都符合此規約,故此情況下它們等價。
規約的設計
前置條件(precondition):對客戶端的約束,客戶端使用方法時必須滿足的條件,使用關鍵詞requires表明。
后置條件(postcondition):對開發者的約束,方法結束時必須滿足的條件,使用關鍵詞effects表明。
- 靜態類型聲明是一種規約,可據此進行靜態類型檢查(static checking)
- 方法前的注釋也是一種規約,但需人工判定其是否滿足
契約:如果前置條件滿足了,后置條件必須滿足;前置條件不滿足,則方法可做任何事情(最好還是處理一下,通過failing fast讓客戶端發現這一問題)。
規約具體設計規則:
- 參數使用@param描述,結果使用@return、@throws描述
- 如果可能,將前置條件寫入@param中,后置條件寫入@return和@throws中
- 除非在后置條件中聲明過,否則方法內部不應該改變輸入參數
- 盡量不設計修改輸入參數的規約,減少使用可變對象
- 描述的功能要單一、簡單、易理解
- 如果規約中需要提到“值”,只能使用抽象空間中的“值”(關聯Chapter 6)
一個具體的規約:
補充Chapter 2 黑盒測試部分:
測試用例不能依賴于具體實現,而必須同客戶端一樣,遵守規約
出處:Chapter 5 課件 P46
規約的比較
可從規約的確定性、陳述性及強度入手進行比較。
假如規約強度S2 >= S1,則有:
- 前置條件S2比S1更弱或相同
- 后置條件S2比S1更強或相同
較強的規約具有更放松的前置條件 + 更嚴格的后置條件
例:
(1)以下三個規約依次增強:
(2)以下兩個規約無法比較強度
相較于第一個規約,第二個的前置條件更弱了;但在滿足第一個規約的前置條件的情況下,第二個規約相較于第一個其后置條件也弱化了(沒有返回最低索引值)。
當規約被增強時:
- 可滿足規約的實現方式更少
- 更多的用戶端可以使用
- 實現者(開發者)的自由度更小,責任更重
- 客戶端(使用者)責任更輕
規約畫圖
以find為例:
可以得到以下結論:
- 某個具體實現,若滿足規約,則落在其范圍內,否則,在其之外
- 程序員可以在規約的范圍內自由選擇實現方式,客戶端無需了解具體使用了哪個實現
- 規約越強,對應的區域越小
如何設計好的規約
- 規約不應太弱,也不能太強(權衡用戶使用與實現難度)
- 在規約里使用抽象類型,可以給方法的實現體與客戶端更大的自由度
- 是否使用前置條件取決于check的代價和方法的使用范圍
Chapter 6:Abstract Data Type
抽象數據類型相關概念
抽象數據類型(ADT)強調“作用于數據上的操作”,程序員和客戶端無需關心數據如何具體存儲的,只需設計/使用操作即可。
抽象數據類型的特性:
- 可能發生表示泄露
- 抽象函數(abstraction function) [AF]
- 表示獨立性(representation independence) [RI]
- 表示不變性(representation invariant)
抽象數據類型的操作分類:
- 構造器(creator):t* → T,可能實現為構造函數或靜態函數(工廠方法)
- 生產器(producer):T+, t* → T
- 觀察器(observer):T+, t* → t
- 變值器(mutator):T+, t* → void | t | T,通常返回void,也可返回非空(如本身、修改結果等)
注:T代表抽象類型自身,t是其他類型,+表示類型出現一次或多次,*表示類型出現零次或多次。
例:
- Integer.valueOf() – Creator
- new ArrayList() – Creator
- Arrays.asList() – Creator
- String.concat() – Producer
- BigInteger.mod() – Producer
- String.toUpperCase() – Producer
- List.size() – Observer
- String.length() – Observer
- Map.keySet() – Observer
- List.addAll() – Mutator
- BufferedReader.readline() – Mutator
設計抽象數據類型
規則:
注:AF、RI應在代碼中以注釋形式寫出,而不能在Javadoc文檔中,防止被外部看到而破壞表示獨立性/信息隱藏。
后兩條規則具體示例:
Chapter 6 課件 P82 ~ P84
測試抽象數據類型
- 測試creators, producers, and mutators:調用observers來觀察結果是否滿足規約
- 測試observers:調用creators, producers, and mutators等方法產生或改變對象,來看結果是否正確
風險:如果被依賴的其他方法有錯誤,可能導致被測試方法的測試結果失效!
表示獨立性
client使用ADT時無需考慮其內部如何實現,ADT內部表示的變化不應影響外部規約和客戶端。
違反表示獨立性的一個示例:
違反原因:ADT修改后客戶端代碼受影響(無法再使用get方法)
保持表示獨立性的一個示例:
表示不變性
表示不變性(represetation invariant, RI)可以看作:
- 某個具體的“表示”是否是“合法的”
- 所有表示值的一個子集,包含了所有合法的表示值
- 一個條件,描述了什么是“合法”的表示值
精確記錄RI——rep中所有fields何為有效
使用checkrep()私有方法檢查RI:
- 在所有可能改變表示的方法內都要檢查
- Observer方法不改變表示,但以防萬一建議也要檢查
如何建立表示不變性:
- 構造器和生產器在創建對象時要確保不變量為true
- 變值器和觀察器執行時必須保持不變性
- 每個方法返回前,用checkRep()檢查不變量是否保持
用ADT不變量可取代復雜的前置條件,相當于將復雜的前置條件封裝到了ADT內部。
抽象函數
首先引入表示空間與抽象空間的概念:
抽象空間(A空間):客戶端看到和使用的值
表示空間(R空間):ADT對于數據的內部表示
ADT開發者要同時關注抽象空間和表示空間,客戶端只需關注抽象空間。
表示空間 → 抽象空間的映射關系:
- 抽象空間的每個值一定有表示空間的值與其對應——滿射
- 一些抽象空間的值可能有多個表示空間的值與之對應——未必單射
- 表示空間中某些值可能沒有對應的抽象空間的值——未必雙射
抽象函數(abstraction function, AF):表示空間和抽象空間之間映射關系的函數,即如何去解釋表示空間中的每一個值為抽象空間中的每一個值。
精準記錄AF——如何解釋每一個表示空間的值(映射關系)
表示不變性(RI)與抽象函數(AF)之間的關系
- 不同的內部表示,需要設計不同的AF和RI
- 選擇某種特定的表示方式R,進而指定某個子集是“合法”的(RI),并為該子集中的每個值做出“解釋”(AF)——即如何映射到抽象空間中的值
- 即使是相同的R、RI,也可能有不同的AF,即“解釋不同”
例:
選擇字符串作為字符集合的表示方式,一種可能的對應RI、AF如下:
則有:
- “ad”、“eeee”、"abcd"滿足RI,“adad”、"abc"不滿足RI
- AF(“acfg”) = {a, b, c, f, g}
- AF(“tv”) = AF(“ttuv”) = AF(“ttuuvv”)
表示泄露
“表示泄露”不僅影響表示不變性,也影響了表示獨立性。
一旦發生,ADT內部表示可能在程序任何位置發生改變(而不是限制在ADT內部)。
除非迫不得已,否則不要把希望寄托于客戶端上,ADT有責任保證自己的不變性,并避免“表示泄露”。
最好的辦法就是使用immutable的類型,徹底避免表示泄露!
表示泄露的安全聲明——給出理由,證明代碼并未對外泄露其內部表示。
有益的可變性
對于不可變的抽象數據類型,它在抽象空間內的抽象值應是不變的,但其內部表示的表示空間取值是可以變化的。
例:一個使用兩個int型變量來表示分數(AF、RI略,表示的分數為numerator / denominator)的ADT,其toString()方法可以修改其內部表示(化簡分數):
/*** @return 分數的可讀字符串表示*/ @override public String toString() {int g = gcd(numerator, denominator);/* 化簡分數,以便輸出人更易讀的值 */numerator /= g;denominator /= g;if (denominator < 0) {numerator = -numerator;denominator = -denominator;}checkRep();return (denominator > 1) ? (numerator + "/" + denominator): (numerator + ""); }注意: 這種mutation僅改變了表示空間的值,并未改變抽象空間的值,對于客戶端來說是利用了”AF未必單射“,從一個表示空間值變成了另一個表示空間值,但這并不代表不可變的類中可以隨意出現mutator方法!
總結
以上是生活随笔為你收集整理的哈工大软件构造课程知识点总结(三)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: threejs创建3d交互地图
- 下一篇: 我的世界服务器修复地图指令,我的世界怎样