## 連載:面向對象葵花寶典:思想、技巧與實踐(29) - 高內聚低耦合
**高內聚低耦合**,可以說是每個程序猿,甚至是編過程序,或者僅僅只是在大學里面學過計算機,都知道的一個簡單的設計原則。
雖然如此流行和人所眾知,但其實**真正理解的人并不多,很多時候都是人云亦云**。
===============================================================
要想真正理解“高內聚低耦合”,需要回答兩個問題:
1)為什么要高內聚低耦合?
2)高內聚低耦合是否意味內聚越高越好,耦合越低越好?
**第一個問題:為什么要高內聚低耦合?**
經典的回答是:降低復雜性。
確實很經典,當然,其實也是廢話!我相信大部分人看了后還是不懂,什么叫復雜性呢?
要回答這個問題,其實可以采用逆向思維,即:如果我們不做到這點,將會怎樣?
首先來看內聚,試想一下,假如我們是低內聚,情況將會如何?
前面我們在闡述內聚的時候提到內聚的關鍵在于“元素的凝聚力”,如果內聚性低,則說明凝聚力低;對于一個團隊來說,如果凝聚力低,則一個明顯的問題是“不穩定”;對于一個模塊來說,內聚性低的問題也是一樣的“不穩定”。具體來說就是如果一個模塊內聚性較低,則這個模塊很容易變化。一旦變化,設計、編碼、測試、編譯、部署的工作量就上來了,而一旦一個模塊變化,與之相關的模塊都需要跟著改變。
舉一個簡單的例子,假設有這樣一個設計不好的類:Person,其同時具有“學生”、“運動員”、“演員”3個職責,有另外3個類“老師”、“教練”、“導演”依賴這個類。
**Person.java**
~~~
package com.oo.cohesion.low;
/**
* “人”的類設計
*
*/
public class Person {
/**
* 學生的職責:學習
*/
public void study() {
//TODO: student's responsibility
}
/**
* 運動員的職責:運動
*/
public void play(){
//TODO: sportsman's responsibility
}
/**
* 演員的職責:扮演
*/
public void act(){
//TODO: actor's responsibity
}
}
~~~
**Teacher.java**
~~~
package com.oo.cohesion.low;
/**
* “老師”的類設計
*
*/
public class Teacher {
public void teach(Person student){
student.study(); //依賴Person類的“學生”相關的職責
}
}
~~~
**Coach.java**
~~~
package com.oo.cohesion.low;
/**
* “教練”的類設計
*
*/
public class Coach {
public void train(Person trainee){
trainee.play(); //依賴Person類的“運動員”職責
}
}
~~~
**Director.java**
~~~
package com.oo.cohesion.low;
/**
* “導演”的類設計
*
*/
public class Director {
public void direct(Person actor){
actor.act(); //依賴Person類“演員”的相關職責
}
}
~~~
在上面的樣例中,Person類就是一個典型的“低內聚”的類,很容易發生改變。比如說,現在老師要求學生也要考試,則Person類需要新增一個方法:test,如下:
~~~
package com.oo.cohesion.low;
/**
* “人”的類設計
*
*/
public class Person {
/**
* 學生的職責:學習
*/
public void study() {
//TODO: student's responsibility
}
/**
* 學生的職責:考試
*/
public void test(){
//TODO: student's responsibility
}
/**
* 運動員的職責:運動
*/
public void play(){
//TODO: sportsman's responsibility
}
/**
* 演員的職責:扮演
*/
public void act(){
//TODO: actor's responsibity
}
}
~~~
由于Coach和Director類都依賴于Person類,Person類改變后,雖然這個改動和Coach、Director都沒有關系,但Coach和Director類都需要重新編譯測試部署(即使是PHP這樣的腳本語言,至少也要測試)。
?
同樣,Coach和Director也都可能增加其它對Person的要求,這樣Person類就需要同時兼顧3個類的業務要求,且任何一個變化,Teacher、Coach、Director都需要重新編譯測試部署。
?
對于耦合,我們采用同樣的方式進行分析,**即:如果高耦合,將會怎樣?**
高耦合的情況下,模塊依賴了大量的其它模塊,這樣任何一個其它依賴的模塊變化,模塊本身都需要受到影響。所以,高耦合的問題其實也是“不穩定”,當然,這個不穩定和低內聚不完全一樣。對于高耦合的模塊,可能本身并不需要修改,但每次其它模塊修改,當前模塊都要編譯、測試、部署,工作量同樣不小。
?
我們同樣以一個樣例來說明。假設我們要設計一個Boss類,Boss是要管整個公司的,那么我們假設這是一家“麻雀雖小五臟俱全”的公司,同時有“研發、測試、技術支持、銷售、會計、行政”等部門。
**Boss.java**
~~~
package com.oo.coupling.high;
/**
* “老板”類
*
*/
public class Boss {
private Tester tester;
private Developer developer;
private Supporter supporter;
private Administration admin;
private Accountant accountant;
/**
* Boss每天檢查工作,看到下面的代碼,是否覺得Boss很忙?
*/
public void check(){
//檢查測試工作
tester.report();
//檢查研發工作
developer.report();
//檢查技術支持工作
supporter.report();
//檢查行政工作
admin.report();
//檢查財務工作
accountant.report();
}
}
~~~
**Accountant.java**
~~~
package com.oo.coupling.high;
/**
* “財務”類
*
*/
public class Accountant {
public void report(){
System.out.print("Accountant report");
}
}
~~~
**Administration.java**
~~~
package com.oo.coupling.high;
/**
* “行政”類
*
*/
public class Administration {
public void report(){
System.out.print("Administration report");
}
}
~~~
**Developer.java**
~~~
package com.oo.coupling.high;
/**
* “開發”類
* @author Administrator
*
*/
public class Developer {
public void report(){
System.out.print("Developer report");
}
}
~~~
**Supporter.java**
~~~
package com.oo.coupling.high;
/**
* “技術支持”類
*
*/
public class Supporter {
public void report(){
System.out.print("Supporter report");
}
}
~~~
**Tester.java**
~~~
package com.oo.coupling.high;
/**
* “測試”類
*
*/
public class Tester {
public void report(){
System.out.print("Tester report");
}
}
~~~
好吧,Boss很忙,我們也很欽佩,但是有一天,研發的同學覺得他們應該做更多的事情,于是他們增加了一個“研究”的工作,且這個工作也不需要報告給Boss,例如:
~~~
package com.oo.coupling.high;
/**
* “開發”類
* @author Administrator
*
*/
public class Developer {
public void report(){
System.out.print("Developer report");
}
/**
* 研發新增加一個研究的任務,這個任務也不需要向Boss匯報
*/
public void research(){
System.out.print("Developer is researching big data, hadoop :)");
}
}
~~~
雖然這個工作不需要報告給Boss,但由于Developer類修改了,那么Boss類就需要重新編譯測試部署。其它幾個Tester、Supporter類等也是類似,一旦改變,即使這些類和Boss類半毛錢關系都沒有,Boss類還是需要重新編譯測試部署,所以Boss類是很不穩定的。
?
所以,無論是“低內聚”,還是“高耦合”,其本質都是“不穩定”,不穩定就會帶來工作量,帶來風險,這當然不是我們希望看到的,所以我們應該做到“高內聚低耦合”。
?
回答完第一個問題后,我們來看第二個問題:高內聚低耦合是否意味著內聚越高越好,耦合越低越好?
按照我們前面的解釋,內聚越高,一個類越穩定;耦合越低,一個類也很穩定,所以當然是內聚越高越好,耦合越低越好了。
但其實稍有經驗的同學都會知道這個結論是錯誤的,并不是內聚越高越好,耦合越低越好,**真正好的設計是在高內聚和低耦合間進行平衡,也就是說高內聚和低耦合是沖突的**。
?
我們詳細來分析一下為什么高內聚和低耦合是沖突的。
對于內聚來說,最強的內聚莫過于一個類只寫一個函數,這樣內聚性絕對是最高的。但這會帶來一個明顯的問題:類的數量急劇增多,這樣就導致了其它類的耦合特別多,于是整個設計就變成了“高內聚高耦合”了。由于高耦合,整個系統變動同樣非常頻繁。
?
同理,對于耦合來說,最弱的耦合是一個類將所有的函數都包含了,這樣類完全不依賴其它類,耦合性是最低的。但這樣會帶來一個明顯的問題:內聚性很低,于是整個設計就變成了“低耦合低內聚”了。由于低內聚,整個類的變動同樣非常頻繁。
對于“低耦合低內聚”來說,還有另外一個明顯的問題:幾乎無法被其它類重用。原因很簡單,類本身太龐大了,要么實現很復雜,要么數據很大,其它類無法明確該如何重用這個類。
?
所以,內聚和耦合的兩個屬性,排列組合一下,**只有“高內聚低耦合”才是最優的設計**。
因此,在實踐中我們需要牢牢記住需要在高內聚和低耦合間進行平衡,而不能走極端。 具體如何平衡,且聽下回分解。
- 前言
- (1) - 程序設計思想的發展
- (2) - 面向對象語言發展歷史
- (3) - 面向過程 vs 面向對象
- (4) - 面向對象是瑞士軍刀還是一把錘子?
- (5) - 面向對象迷思:面向對象導致性能下降?
- (6) - 不要說你懂“類”
- (7) - “對象”新解
- (8) - “接口” 詳解
- (9) - “抽象類” 詳解
- (10) - “抽象” 詳解
- (11) - “封裝” 詳解
- (12) - “繼承” 詳解
- (13) - “多態” 詳解
- (14) - 面向對象開發技術流程
- (15) - 需求詳解
- (16) - 需求分析終極目的
- (17) - 需求分析518方法
- (18) - 用例分析
- (19) - 功能點提取
- (20) - 用例圖的陷阱
- (21) - SSD
- (22) - 領域模型
- (23) - 領域建模三字經
- (24) - 設計模型
- (25) - 類模型
- (26) - 類模型三板斧
- (27) - 動態模型設計
- (28) - 設計原則:內聚&耦合
- (29) - 高內聚低耦合
- (30) - SRP原則
- (31) - OCP原則
- (32) - LSP原則
- (33) - ISP原則
- (34) - DIP原則
- (35) - NOP原則
- (36) - 設計原則如何用?
- (37) - 設計模式:瑞士軍刀 or 錘子?
- (38) - 設計模式之道
- (39) - 設計原則 vs 設計模式
- (40) - DECORATOR模式
- (完)- 書籍已經出版