哈工大 軟件構造課程 考點復習總結(第三章)
-
數據類型
Primitive types 基本數據類型(8種) |
|
Short、int 、long、float、double、boolean、char、byte |
如:String、BigInteger |
只有值,沒有ID(無法與其他值區分),不能賦值為null; immutable |
有值,也有ID; 部分mutable,部分immutable |
在棧中分配內存,代價低 |
在堆中分配內存,代價高 |
-
靜態類型檢查&動態類型檢查
靜態類型檢查 |
動態類型檢查 |
(靜態類型語言 如java) |
(動態類型語言 如python) |
提高程序的正確性和健壯性 |
|
關於"類型"的檢查,不考慮值(不知道運行時會是什麽值) |
關於"值"的檢查 |
-
Mutable & Immutable
Immutable 不可變數據類型
Immutable 不可變數據類型
優點
優點:安全
優點:最少化拷貝以提高效率獲得更好的性能,適合於在多個模塊之間共享數據
缺點
缺點:頻繁修改產生大量臨時拷貝,需要垃圾回收·
缺點:不安全
其他
一旦被創建,其值不能改變
對於引用類型,加final限制不能改變引用
安全地使用可變類型:局部變量(不涉及共享,且只有一個引用)
如果有多個引用(別名),不安全
Defensively copy 防禦式拷貝:返回全新的對象
盡可能用immutable!
-
Snapshot Diagram 畫法:
-
基本類型:單獨一個常量
引用類型:圈住!
-
重分配:
不可變類型(用雙線橢圓),修改引用
可變類型:修改值
-
引用:
可變引用:單線箭頭
不可變引用:雙線箭頭
-
Specification
-
作用
-
規約可以隔離"變化",無需通知客戶端
- 規約可以提高代碼效率
- 規約扮演"防火墻"角色
- 解耦,不需要了解具體實現
-
規約可以隔離"變化",無需通知客戶端
- 內容:只講"能做什麽",而不講"怎麽實現"
-
Behavior equivalence 行為等價性
是否可以相互替換
- 站在客戶端的視角看行為等價性,不同的行為,對用戶來說(根據用戶需求)可能等價!
- 根據規約判斷行為等價,兩個方法符合同一個規約,則等價
-
規約的結構:
- Pre-condition
- Post-condition
- Exceptional behavior 異常行為,如果違背了前置條件,會發生什麽
-
規約的強度與替換
Spec變強:更放松的前置條件(前置條件更弱)+更嚴格的後置條件(後置條件你更強),
兩條件同時變強或變弱則無法比較。
若規約強度S2>=S1,則可以用S2替換S1。
-
deterministic spec & undetermined spec 確定的規約和欠定的規約
- 確定的規約:給定一個滿足前置條件的輸入,其輸出唯一、明確
- 欠定的規約:同一個輸入可以有多個輸出(多次執行輸出可能不同)
-
Declarative spec & operational spec 聲明式規約和操作式規約
- 操作式規約:如 偽代碼
- 聲明式規約:沒有內部實現的描述,只有"初-終"狀態
聲明式規約更有價值!
內部實現的細節不在規約裏呈現,而放在代碼實現體內部註釋裏呈現。
-
Diagraming specification
規約定義一個區域,該區域包含所有可能的實現方式。
空間中的每個點表示一種方法的實現。
對於某個具體實現,若滿足規約,則落在其區域內。
更強的規約表達為更小的區域。
-
Quality of specification 規約質量
- 內聚性:spec描述的功能應單一、簡單、易理解
- 運行結果信息豐富(可能的改變,以及返回值等),不能讓客戶端產生理解上的歧義
- 足夠強(如postcondition中充分闡述各種情況)
- 適當弱(太強的規約,在很多特殊情況下難以達到)
- 在規約裏使用抽象類型(在java中,經常使用interface,如Map、List,而不是HashMap、ArrayList),可以給方法的實現體和客戶端更大的自由度
-
使用前置條件和後置條件?
客戶端不喜歡太強的pre-condition,不滿足precondition的輸入會導致失敗
So:不限定太強的precondition,而在postcondition中拋出異常:輸入不合法,
fail fast,避免fail大規模擴散
是否使用前置條件取決於:
- check(檢查參數合法性)的代價
-
方法的使用範圍:
- 如果只在類內部使用(private),則可以不使用precondition,在使用該方法的各個位置進行check
- 如果在其他地方使用(public),則必須使用precondition,若client不滿足則拋出異常
-
Pre-condition and post-condition 前置條件和後置條件
Pre-condition 前置條件(requires) |
Post-condition 後置條件(effects) |
@param |
@return @throws |
對客戶端的約束 在使用方法時必須滿足的條件 |
對開發者的約束 方法結束時必須滿足的條件 |
契約:如果前置條件滿足了,後置條件必須滿足 |
|
除非在後置條件中聲明,否則方法內部不應該改變輸入參數。 盡量不設計mutating的spec,否則容易引發bugs。 盡量避免使用mutable對象。 避免使用可變的全局變量。 |
ADT
-
ADT及其四種操作
抽象類型:強調"作用於數據上的操作",程序員和client無需關心數據如何具體存儲,只需設計/使用操作即可。
ADT由操作定義,與其內部實現無關。
可變數據類型:提供了可改變其內部數據值的操作;
不可變數據類型:其操作不改變內部值,而構造新的對象。(沒有mutators)
ADT操作分類:
-
Creators 構造器:
不利用該類型對象產生一個新的對象
可能實現為構造函數或靜態函數(factory method)
-
Producers 生產器:
用已有該類型對象產生新對象
如string.concat()(連接兩個字符串,產生一個新的字符串)
-
Observers 觀察器
如list.size()返回int(不同於原類型)
-
Mutators 變值器(改變對象屬性的方法)
通常範圍void,如果返回void,則必然意味著它改變了某些對象的內部狀態
也可能範圍非空類型(如容器類的put、add方法)
-
Representation Independence 表示獨立性
-
表示獨立性:client使用ADT時無需考慮其內部如何實現,ADT內部表現的變化不應該影響外部spec和客戶端。
-
Representation exposure 表示泄漏
如client能直接接觸類成員變量。
表示泄漏影響表示不變量,也影響表示獨立性:無法在不影響客戶端的情況下改變其內部表示。
避免方法:private、final、defensive copy
-
Invariants 不變量 & Representation Invariant 表示不變量
ADT應保持其不變量在任何時候總是true;
ADT負責其不變量,與client的任何行為無關。
作用:保持程序的"正確性",容易發現錯誤。
-
Abstraction Function 抽象函數
表示空間R |
抽象空間A |
值的實際實現本質 |
抽象表示(client看到和使用的值) |
ADT實現者關註表示空間R |
用戶關註抽象空間A |
R到A的映射
一定是滿射:A中元素總有R中具體的實現
未必是單射:A中一個元素在R中可能有多重實現方式
未必是雙射:R中表示不符合A中需求(如圖中"abbc")
抽象函數AF:R和A之間映射關系的函數
AF:R->A
對於RI :R-> Boolean
RI:某個具體的"表示"是否合法;表示值的一個子集,包含所有合法的表示值;一個條件,描述了什麽是"合法"表示值。
-
Documenting AF 、RI、Safety from Rep Exposure
選擇某種特定的表示方式R
進而指定某個子集是"合法"的(RI)
並為該子集中的每個值做出"解釋"(AF)
即 如何映射
Safety from Rep Exposure
證明代碼並未對外泄露其內部表示
保證不變量為true,不變量:
- 通過creators和producers創建
- 受mutators和observers保護
- 無表示泄漏發生
OOP
-
Interface 接口
接口的成員變量默認用final關鍵字修飾,故必須有初值,可用public,default修飾,可用static修飾。
接口的方法只能被public、default、abstract、static、strictfp(嚴格浮點運算)修飾。
-
Inheritance、override 繼承和重寫
Strict inheritance 嚴格繼承:子類只能添加新方法,無法重寫超類(父類)中的方法(final限制)。
考慮final修飾類、方法、屬性時的不同作用。
Override 方法:具有一致的signature,復用的基本機制。
-
Polymorphism ,subtyping and overloading 多態,子類型化,重載
三種多態:
-
Ad hoc polymorphism (特殊多態)
用於function overloading(功能重載),即重載
-
Parametric polymorphism (參數化多態)
泛型
- Subtyping (subtype polymorphism / inclusion polymorphism )(子類型多態、包含多態)
-
Overloading 重載
-
重載條件:
- 方法名相同
- 參數列表不同,即參數類型、個數、類型順序至少有一項不相同
- 返回值類型可以不同
- 方法的修飾符可以不同
- 可以拋出不同的異常
- 可以在類內重載,也可以在子類重載
-
重載是一種靜態多態,靜態類型檢查
(static dispatch 靜態分派)並在編譯階段決定具體執行哪個方法(即對方法的調用取決於編譯時聲明的引用的類型)
而重寫(dynamic dispatch 動態分派)則進行動態類型檢查,根據運行時堆中的實例類型選擇方法。
-
重載條件:
-
Generic 泛型
- 通配符 <?> :只有使用泛型的時候出現,不能在定義中出現。
-
類型擦除:編譯後、運行時類型擦除
List<Integer> -> List
註意可能引起重載編譯錯誤。
運行時不能用 instanceof 檢查泛型。
-
不能創建泛型數組
不能用在靜態變量
不能創建對象(不能new)
-
Subtypes
超類的子類型,如:ArrayList和LinkedList是List的子類型。
子類型的規約不能弱化超類型的規約。
- 子類型多態:不同類型的對象可以統一處理而無需區分(不加區分地調用同樣的方法等),從而隔離變化
- LSP(Liskov Substitution Principle) 如果S是T的子類型,那麽T的對象可以被S的對象替換。
-
Type casting 類型轉換
避免向下類型轉換。
- Dispatch 分派
Static dispatch 靜態分派 |
Dynamic dispatch 動態分派 |
將調用的名字與實際方法的名字聯系起來(可能有多個) |
決定具體執行哪一個操作 |
重載,在編譯階段即可確定執行哪個具體操作 |
重寫,在運行時決定 |
Early/static binding |
Lade/dynamic binding |
綁定static、private、final方法時發生 |
重寫父類子類的同樣方法 |
-
equals()
-
引用等價性 ==
比較內存地址ID
用於比較基本數據類型
-
對象等價性 equals()
驗證正確性:reflexive 自反性、symmetric 對稱性、transitive 傳遞性、非空(a.equals(null) return false)
-
hashCode()
等價的對象必須有相同的hashCode
Rule:重寫equals時重寫hashcode
-
Equality of Mutable Types 可變對象的等價性
-
Observational equality 觀察等價性 |
Behavioral equality 行為等價性 |
在不改變狀態的形況下,兩個mutable看起來是否一致 |
調用對象的任何方法都展示出一致的結果 |
調用observer,producer,creator |
調用任何方法,包括mutator |
當前情況下,看起來(如成員變量)相同 |
經過改變後,依然相同(只是別名引用) |
對不可變類型,觀察等價性和行為等價性完全等價。
對可變類型,往往傾向於實現嚴格的觀察等價性。(但有時觀察等價性可能導致bug,甚至破壞RI)
對可變類型,應當實現行為等價性,即只有指向內從空間中同樣的objects才相等(即equals比較引用,如==而hashcode把引用映射為一個值)。
所以對可變類型,無需重寫equals和hashcode,直接繼承object。(比較引用)
若一定要判斷兩個可變對象是否一致,最好定義一個新的方法。
哈工大 軟件構造課程 考點復習總結(第三章)