這邊文章主要來源極客時間的設(shè)計模式之美,非常棒的一個教程,大家一定要買這個課程!一定要!
什么是高內(nèi)聚
所謂高內(nèi)聚,就是指相近的功能應(yīng)該放到同一個類中,不相近的功能不要放到同一個類中。 相近的功能往往會被同時修改,放到同一個類中,修改會比較集中,代碼容易維護。
什么是松耦合
所謂松耦合是說,在代碼中,類與類之間的依賴關(guān)系簡單清晰。即使兩個類有依賴關(guān)系,一
個類的代碼改動不會或者很少導(dǎo)致依賴類的代碼改動。
內(nèi)聚和耦合的關(guān)系
“高內(nèi)聚”有助于“松耦合”,同理,“低內(nèi)聚”也會導(dǎo)致“緊耦合”
什么是迪米特法則?
Each unit should have only limited knowledge about other units: only units “closely” related to the current unit. Or: Each unit should only talk to its friends; Don’t talk to strangers.
每個模塊(unit)只應(yīng)該了解那些與它關(guān)系密切的模塊(units: only units “closely” related to the current unit)的有限知識(knowledge)?;蛘哒f,每個模塊只和自己 的朋友“說話”(talk),不和陌生人“說話”(talk)。
不該有直接依賴關(guān)系的類之間,不要有依賴
簡單的爬蟲代碼
NetworkTransporter:負(fù)責(zé)底層網(wǎng)絡(luò)通信,根據(jù)請求獲取數(shù)據(jù)
HtmlDownloader:用來通過 URL 獲取網(wǎng)頁
Document:表示網(wǎng)頁文檔,后續(xù)的網(wǎng)頁內(nèi)容抽取、分詞、索引都是 以此為處理對象
public class NetworkTransporter {
// 省略屬性和其他方法...
public Byte[] send(HtmlRequest htmlRequest) {
}
}
public class HtmlDownloader {
private NetworkTransporter transporter;// 通過構(gòu)造函數(shù)或 IOC 注入
public Html downloadHtml(String url) {
Byte[] rawHtml = transporter.send(new HtmlRequest(url));
return new Html(rawHtml);
}
}
public class Document {
private Html html;
private String url;
public Document(String url) {
this.url = url;
HtmlDownloader downloader = new HtmlDownloader();
this.html = downloader.downloadHtml(url);
}
}
代碼中的問題?
NetworkTransporter的問題?
??作為一個底層網(wǎng)絡(luò)通信類,我們希望它的功能 盡可能通用,而不只是服務(wù)于下載 HTML,所以,我們不應(yīng)該直接依賴太具體的發(fā)送對象 HtmlRequest。從這一點上講,NetworkTransporter 類的設(shè)計違背迪米特法則,依賴了 不該有直接依賴關(guān)系的 HtmlRequest 類。
??我們應(yīng)該如何進行重構(gòu),讓 NetworkTransporter 類滿足迪米特法則呢?我這里有個形象 的比喻。假如你現(xiàn)在要去商店買東西,你肯定不會直接把錢包給收銀員,讓收銀員自己從里面拿錢,而是你從錢包里把錢拿出來交給收銀員。這里的 HtmlRequest 對象就相當(dāng)于錢 包,HtmlRequest 里的 address 和 content 對象就相當(dāng)于錢。我們應(yīng)該把 address 和 content 交給 NetworkTransporter,而非是直接把 HtmlRequest 交給NetworkTransporter。
send(String address, Byte[] data)
HtmlDownloader
這個類沒什么問題,只需要把入?yún)⑿薷囊幌?/p>
Document
這個類的問題比較多,主要有三點。
第一,構(gòu)造函數(shù)中 的 downloader.downloadHtml() 邏輯復(fù)雜,耗時長,不應(yīng)該放到構(gòu)造函數(shù)中,會影響代 碼的可測試性。代碼的可測試性我們后面會講到,這里你先知道有這回事就可以了。
第二, HtmlDownloader 對象在構(gòu)造函數(shù)中通過 new 來創(chuàng)建,違反了基于接口而非實現(xiàn)編程的 設(shè)計思想,也會影響到代碼的可測試性。
第三,從業(yè)務(wù)含義上來講,Document 網(wǎng)頁文檔 沒必要依賴 HtmlDownloader 類,違背了迪米特法則。
public class Document {
private Html html;
private String url;
public Document(String url, Html html) {
this.html = html;
this.url = url;
}
}
//工廠對象
public class DocumentFactory {
//
private HtmlDownloader downloader;
//
public DocumentFactory(HtmlDownloader downloader) {
this.downloader = downloader;
}
//
public Document createDocument(String url) {
Html html = downloader.downloadHtml(url);
return new Document(url, html);
}
}
有依賴關(guān)系的類之間,盡量只依賴必要的接口
public class Serialization {
public String serialize(Object object) {
String serializedResult = ...;
//...
return serializedResult;
}
public Object deserialize(String str) {
Object deserializedResult = ...;
//...
return deserializedResult;
}
}
?&emsp單看這個類的設(shè)計,沒有一點問題。不過,如果我們把它放到一定的應(yīng)用場景里,那就還有繼續(xù)優(yōu)化的空間。假設(shè)在我們的項目中,有些類只用到了序列化操作,而另一些類只用到反序列化操作。那基于迪米特法則后半部分“有依賴關(guān)系的類之間,盡量只依賴必要的接口”,只用到序列化操作的那部分類不應(yīng)該依賴反序列化接口。同理,只用到反序列化操作的那部分類不應(yīng)該依賴序列化接口。
?&emsp根據(jù)這個思路,我們應(yīng)該將 Serialization 類拆分為兩個更小粒度的類,一個只負(fù)責(zé)序列化 (Serializer 類),一個只負(fù)責(zé)反序列化(Deserializer 類)。拆分之后,使用序列化操作 的類只需要依賴 Serializer 類,使用反序列化操作的類只需要依賴 Deserializer 類。拆分 之后的代碼如下所示:
public class Serializer{
public String serialize(Object object) {
String serializedResult = ...;
//...
return serializedResult;
}
}
public class Deserializer{
public Object deserialize(String str) {
Object deserializedResult = ...;
//...
return deserializedResult;
}
}
?&emsp不知道你有沒有看出來,盡管拆分之后的代碼更能滿足迪米特法則,但卻違背了高內(nèi)聚的設(shè) 計思想。高內(nèi)聚要求相近的功能要放到同一個類中,這樣可以方便功能修改的時候,修改的 地方不至于過于分散。對于剛剛這個例子來說,如果我們修改了序列化的實現(xiàn)方式,比如從 JSON 換成了 XML,那反序列化的實現(xiàn)邏輯也需要一并修改。在未拆分的情況下,我們只需要修改一個類即可。在拆分之后,我們需要修改兩個類。顯然,這種設(shè)計思路的代碼改動范圍變大了。
?&emsp如果我們既不想違背高內(nèi)聚的設(shè)計思想,也不想違背迪米特法則,那我們該如何解決這個問 題呢?實際上,通過引入兩個接口就能輕松解決這個問題,具體的代碼如下所示。實際上, 我們在第 18 節(jié)課中講到“接口隔離原則”的時候,第三個例子就使用了類似的實現(xiàn)思 路,你可以結(jié)合著一塊兒來看。
