面向對象設計原則
- 單一職責原則 (SRP)
- 開閉原則 (OCP)
- Liskov 替換原則 (LSP)
- 接口隔離原則 (ISP)
依賴倒置原則 (DIP)
SOLID
OCP: 開閉原則
- 軟件模塊對擴展是開放的
- 當需求發生改變時,可以對模塊進行擴展
- 軟件模塊對修改是封閉的
- 對模塊進行擴展時, 無須改動模塊的源代碼。
- 似乎是矛盾的 ?
面向對象設計的原則 (2) : 開閉原則
缺點:
- 對擴展開放: 可以添加新的水果類
- 但是每次添加新的水果類,就需要修改ShopCart中的邏輯 !
重構後
- 對擴展開放
- 可以任意的添加新的水果類:香蕉,西瓜…
- 對修改是封閉的
- 對於ShopCart中的計算邏輯不用修改。
- 模塊依賴於一個固定抽象體,所以對於更改是關閉的。同時通過這個抽象體派生,也可以擴展此模塊的行爲。所以關鍵是抽象 !
例子
重構後
- 在許多方面,OCP都是面向對象設計的核心所在。遵循這個原則可以帶來面向對象技術所聲稱的巨大好處(也就是 靈活性,可重用性,以及可維護性)。然而,並不是說只要使用一種面嚮對象語言遵循了這個原則。對於應用程序中的每個部分都肆意的進行抽象並不是一個好的主意。正確的做法是,開發人員應該僅僅對程序中頻繁出現的變化的那部分做出抽象。拒接不成熟的抽象和抽象本身一樣重要。
Liskov 替換原則 (LSP)
- 子類型能夠完全替換父類型,而不會讓調用父類型的客戶程序從行爲上有任何改變
- 難道多態不就是爲了達到這個目標嗎?
LSP: 正方形 is a 長方形?
LSP: 鳥都會飛嗎
某個程序員創建了一個鴕鳥類
繼承的目的
- 重用父類的代碼
- 更重要的是, 複用那些使用父類的代碼(例如processAll)!!
LSP實際上是確保我們做的抽象不會被子類破壞
LSP和契約式設計
Bertrand Meyer 在 1988 年闡述了 LSP 原則與契約式設計之間的關係。使用契約式設計,類中的方法需要聲明前置條件和後置條件。前置條件爲真,則方法才能被執行。而在方法調用完成之前,方法本身將確保後置條件也成立。
Rectangle.setWidth的後置條件
- Assert (( width==w ) && (height == old.height))
- 很明顯, Square 違反了這個後置條件
當通過基類(父類)的接口使用對象時, 用戶只知道基類的前置條件和後置條件
派生類(子類) 只能使用相等或者更弱的前置條件類替換父類的前置條件
接口隔離原則(ISP)
客戶端不應該依賴它不需要的接口
類間的依賴關係應該建立在最小的接口上
- 使用多個專門的接口比使用單一的總接口要好。
防止接口污染
ISP: ATM例子
ISP: 咖啡機例子
重構後
依賴倒置原則(DIP)
- 高層模塊不應該依賴於低層模塊,二者都應該依賴於抽象
- 抽象不應該依賴於細節。細節應該依賴於抽象。
DIP: 熔爐的例子
DIP: 打印機的例子
重構後
使用傳統的過程化程序設計所創建出來的依賴關係結構,策略是依賴於細節的。這樣會導致策略受到細節改變的影響。面向對象的程序設計倒置了依賴關係結構,使得細節和策略都依賴於抽象,並且常常是客戶擁有服務接口。
依賴關係的倒置正好是面向對象設計的標誌所在。如果程序的的依賴關係是倒置的,他就是面向對象的設計。如果程序的依賴關係不是倒置的它就是過程化的設計。
依賴倒置原則對於創建可重用框架來說是必須的。同時對於構建在變化面前富有彈性的代碼也是非常重要的。由於抽象和字節被彼此隔離,所以代碼也非常容易維護。