面向對象技巧
內聚
當模塊的元素全部專注於模塊的職責的時候,即使元素間的結合不是很緊密,也符合內聚性的要求。簡單的說,就是“不要掛羊頭賣狗肉”。
- 巧合內聚
模塊內部的元素被划分在一起,僅僅是因為“巧合”。
- 邏輯內聚
模塊內部元素之所以被划分在一起,是因為這些元素邏輯上屬於同一個比較寬泛的類別。
比如將鼠標、鍵盤划分入輸入類,將打印機、顯示器划分為輸出類。
- 時序內聚
模塊內部的元素之所以被划分在同一個模塊中,是因為這些元素必須按照固定的“時間順序”進行處理。
- 過程內聚
模塊內部的元素之所以被划分在同一個模塊中,是因為這些元素必須按照固定的“過程順序”進行處理。
判斷文件是否存在、判斷文件是否有權限、打開文件、讀(或者寫)文件,那么把這些處理封裝在一個函數中,它們之間的內聚就是“過程內聚”。
- 交互內聚
模塊內部的元素之所以被划分在同一模塊中,是因為這些元素都操作相同的數據。
交互內聚最常見的就是數據結構的類定義了,例如 Java HashMap 的 get、put、clear 等操作。
- 功能內聚
模塊內部的元素之所以被划分在同一模塊中,是因為這些元素都是為了完成同一項任務。
耦合
耦合和內聚是相反的:內聚關注模塊內部的元素結合程度,耦合關注模塊之間的依賴程度。
- 無耦合
無耦合意味着模塊之間沒有任何關系或者交互。
- 消息耦合
系統/子系統:兩個系統交互的協議數據,例如:HTTP POST 數據,Java RPC、Socket 數據等;
類/函數:函數的參數即“消息”。例如:A 類的函數調用了 B 類的某個函數,傳入的參數就是消息。
- 數據耦合
兩個模塊間通過參數傳遞基本數據,稱為數據耦合。
- 數據結構耦合
數據結構耦合和數據耦合是比較相近的,主要差別在於數據結構耦合中傳遞的不是基本數據,而是數據結
構數據。
- 控制耦合
當一個模塊可以通過某種方式來控制另外一個模塊的行為時,稱為控制耦合。
- 外部耦合
為了讓香港的插頭也能在大陸用,我們就需要買一個“電源轉換頭”,其作用就是能夠讓原來不兼容的香港插頭和大陸插座兩個設備能夠連接起來,但原有兩個設備內部都繼續保持原樣。
這里的香港插頭和大陸插座就是外部耦合關系,外部設備是“電源轉換頭”
在軟件系統,外部依賴最典型的莫過於各種“proxy”模塊或者子系統了。比如說 A 系統輸出 XML 格式,
B 系統只能接收 JSON 格式的數據,為了能夠讓兩個系統連接起來,需要開發一個轉換程序,完成格式轉換。
簡單來說,很多東西不是你想改就能改的!使用外部依賴一般的主要原因如下:
- 已有系統無法改動:比如說你買的香港插頭和大陸插座,除非你真的是吃飽了沒事做或者要挑戰自己,否則一般人都不會自己動手將香港插頭改為大陸插頭,或者將大陸插座改為香港插座。
- 已有系統已經成熟,改動需要很高的成本,也會導致系統不穩定。
- 已有系統支持很多其它系統,不能為了你的系統單獨修改。
高內聚低耦合
如果我們不做到這點,將會怎樣?
- 如果一個模塊內聚性較低,則這個模塊很容易變化。一旦變化,設計、編碼、測試、編譯、部署的工作量就上來了,而一旦一個模塊變化,與之相關的模塊都需要跟着改變。
高內聚低耦合是否意味着內聚越高越好,耦合越低越好?
-
並不是內聚越高越好,耦合越低越好,真正好的設計
是在高內聚和低耦合間進行平衡,也就是說高內聚和低耦合是沖突的。 -
最強的內聚莫過於一個類只寫一個函數,這樣內聚性絕對是最高的。但這會帶來一個明顯
的問題:類的數量急劇增多,這樣就導致了其它類的耦合特別多,於是整個設計就變成了“高內聚高耦合”了。由於高耦合,整個系統變動同樣非常頻繁。 -
對於耦合來說,最弱的耦合是一個類將所有的函數都包含了,這樣類完全不依賴其它類,耦合性是最低的。但這樣會帶來一個明顯的問題:內聚性很低,於是整個設計就變成了“低耦合低內聚”了。由於低內聚,整個類的變動同樣非常頻繁。
-
對於“低耦合低內聚”來說,還有另外一個明顯的問題:幾乎無法被其它類重用。原因很簡單,類本身太龐大了,要么實現很復雜,要么數據很大,其它類無法明確該如何重用這個類。