軟件構造-面向可複用性的編程-複習筆記

 

這篇博客主要摘錄我在複習軟件構造的可複用部分的感悟以及對一些重點內容的摘錄java

可複用主要包括了兩個重點內容:程序員

  • 繼承與面對對象編程
  • 設計模式

其他部分就是以這兩個重點爲核心的一些補充,這也是軟件構造的的一個很是重要的思想部分編程

 

這一部份內容其實是對前面ADT,specification相關內容的繼續,那些內容制定了編程過程當中的一些規範,這一章就是在規範的基礎上研究軟件的頂層設計。正由於有了specification的保護,使得咱們能夠不用關心類的內部實現,而可以把注意力放在功能塊之間的設計上來。設計模式

 

繼承與OOP

接口,繼承與泛型

 關於繼承的約定

若是B是A的子類型,那麼A與B之間應該有這樣的關係:框架

  • 全部的B都是A

而這個關係在程序中的體現就是:ide

  • B的specification必定要強於A的specification

而這個約定又分爲兩種,一種是可以經過靜態檢查發現的。也就是A中全部的方法都必須在B中出現,並且方法的返回值,簽名等等都必須相同。post

當咱們使用extends關鍵詞來定義A的子類型的時候,靜態檢查會幫咱們保證這一性質。設計

可是還有一種是靜態檢查所發現不了的,也就是pre-condition和post-condition3d

繼承保證了共性,可是子類型也必定有其個性。子類型能夠經過添加本身的方法來爲本身添加細化的性質,也能夠重寫父類的方法,所以就有可能改變父類的方法的precondition與postconditioncode

要想知足B的specificatioin強於A的specifition這一性質,就必須保證:

  • B的重寫方法的precondition不能強於A中的原方法
  • B的重寫方法的postcondition不能弱於A中的原方法

這是ide沒法靜態檢查獲得的,須要程序員去保證。

一道例題

MIT的readingd提供了一道例題:

這道例題能夠這樣思考

題目中父類是rectangle,而後嘗試添加一個square的子類。

乍一想是很合理的,每個square固然是一個rectangle了,specification明明強化了——多了rectangle相鄰邊必須相等的保證。

可是square中對rectangle的setSize方法的重寫,卻沒有一個可以知足條件的,爲何?

由於題中給定是mutable的類型,應該這樣想:

每個square必定是一個rectangle,可是可以任意設定兩條邊長的square並非可以任意設定兩條邊長的rectangle

 

另外一道例題

這題選B

我是這麼理解了,

Double對Number是增強了postCondition,因此是符合的

事實上根據老師上課時ppt5-2中的內容,在繼承關係中,子類中對父類方法的重寫,變量能夠逆變是沒有錯的,只是在java中並不支持這種寫法。

 

所以,咱們在須要用到變量的逆變的時候,應該不使用@Override標記,而是直接改寫。這樣對編譯器來講是父類方法的重載,只是在概念上咱們會將之當成重寫罷了。

根據Liskov替換原則,這樣的改寫是知足條件的,也就是若是把任何對父類的引用改爲子類,也不會產生任何問題,子類的結果只會更嚴格。

 

關於Liskov原則的理解

LIskov對繼承的規定有以下幾點:

  • 前置條件不能強化
  • 後置條件不能弱化
  • 不變量必須保持
  • 子類型方法參數:逆變
  • 子類型方法返回值:協變
  • 異常類型:協變

其實我傾向於將子類型方法參數與子類型方法返回值的規定歸於對前置條件與後置條件限制

也就是:協變是一種強化,縮小範圍,更加具體。而逆變是一種弱化,擴大範圍,更加抽象。

在此我有一個疑問:爲何liskov原則不把子類型方法的參數與變量歸於前置條件與後置條件呢?

泛型的繼承規則

一個讓初學者感到出人意料的事實:

ArrayLIst<String> 是 List<String> 的子類型

List<String> 不是 List<Object> 的子類型

java是徹底屏蔽了泛型中的繼承檢查,只要是不一致的類型,都會被簡單的當成不一樣類型

除非用上通配符 <?>

 <?> 是任意E : <E> 的超類型

 

 這樣也沒有問題:

 

 這樣也能夠:

經過這幾個小例子能夠發現,使用了通配符的泛型與外面的類型是同等進行靜態檢查的。

也就是說: ArrayList<Integer> 是 List<?> 的子類型,由於內外都是

 

關於equality的討論

在離散數學當中,等價性的原則有三

  • 自反性
  • 對稱性
  • 傳遞性

而在java當中,不一樣ADT的等價性在知足這幾個條件的基礎上,還存在着更加嚴格的要求。

我在這裏說的是ADT的等價性而不是對象的等價性,由於對象等價的概念是物理層面的,簡單的理解就是調用「==」或者equals()方法可以獲得真值,詳細一些來講就是從java的運行時角度來看,兩個對象的數據值是同樣的。

而ADT則是抽象層面的東西,是咱們在設計程序的時候本身定義出來的,可能有不一樣的規則:
好比說,若是我在討論動物與植物,那麼我能夠在概念上把兔子和猴子當作等價的,而猴子和樹是不等價的。

而咱們在設計ADT時,就必須使咱們定義的等價遵循上面的三個條件。

 

額外的規則

程序設計中的等價是爲程序行爲服務的,所以應該具備更加多的限制

  • equality應當遵循等價的基本原則,也就是自反,對稱和傳遞,這是基礎
  • equals應當具備行爲連續性,若是equals中比較的元素的值沒有改變,那麼對該實例的重複調用某一個方法應當始終返回相同的結果。
  • 若x不是null,x.equals(null)應當永遠返回false
  • equals應當與hashcode的比較具備相同的結果

觀察等價性與行爲等價性

觀察等價性

即「看起來同樣」。

指的是兩個引用,若是它們是觀察等價的,在不對它們調用mutator方法的狀況下,任何obsever方法的調用都沒法區分這兩個引用。

也就是說,client能夠只經過observers就區分兩個具備觀察等價性的實例對象。

觀察等價性側重於兩個對象在程序運行的當前時刻是處於相同狀態的

行爲等價性


即「用起來同樣」

對於哪怕引用,哪怕調用了mutators方法,兩個ADT的行爲始終是同樣的(這個行爲固然也能夠包括observers,因此行爲等價性是包含觀察等價性的)。

行爲等價性側重於兩個對象如今是同樣的,在將來也會一直是同樣的。

行爲等價性的規定如此嚴格,通常來講,只有兩個指向同一個對象的引用才能知足這個條件。

這對mutable對象來講是合理的,由於mutable對象當中存在着太多的不肯定性,好比說經典的「將list放入set中」的例子

 設計模式

設計模式相關的內容汗牛充棟,對於設計模式自己,我沒有什麼好補充的,由於我寫博客的目的是記錄感想大於總結知識點,我主要想要談一下對設計模式的理解。

設計模式是頂層設計。

若是說繼承與面對對象是代碼層面的複用,那麼設計模式就是模塊層面的複用,經過一些很好用的框架來方便咱們的程序的編寫。

起初我對設計模式十分抵觸。緣由是我對java語法的掌握不夠熟練,所以在使用設計模式中的工廠模式的時候,在繼承,泛型,類型轉換的各類錯誤之中摸爬滾打了好久,吃了不少苦,最後也沒有體會到工廠模式的妙處所在,反而爲其所累。

我是這麼想的:

  • 設計模式不是目的,若是爲了使用設計模式而使用設計模式,反而會使程序更加複雜而難以理解。

個人代碼量實在過小,功能也很簡單,因此沒有嚐到設計模式的好處。

相關文章
相關標籤/搜索