這一章書中全是理論性的東西,再加上中文翻譯肯定有詞不達意的地方(并未有意冒犯譯者,sry),初讀起來并沒有理解,遂仔細思考后又讀了兩遍才敢總結。
作者在本章提到“邊界”的概念,我個人的理解是:使用外部代碼(第三方庫提供的API、或者其他模塊的服務)與自身項目代碼結合的時候,自身項目調用外來代碼以及外來代碼提供的功能,這兩部分代碼就是邊界。如果干凈利落的將這些代碼整合,就能保持軟件邊界的整潔。
一、使用第三方代碼(良好的實踐范例)
第三方jar包和框架提供者追求通用性,這樣就能在多個環(huán)境下工作,吸引更多的用戶。而使用者則想要集中滿足特定需求的接口,這樣方便開發(fā)需求。正是因為服務提供者和使用者的目標的差異,導致了在系統(tǒng)邊界上可能會出現(xiàn)問題。
舉個例子,以java.util.Map為例。Map中擁有豐富的功能(這對應了上面說的第三方jar包和框架提供者追求通用性)。但是,這也會付出一些代價。比如,應用程序可能構造一個Map對象并到處傳遞它的引用。 假設我們原來設計的初衷是:Map對象的所有接收者都不要刪除映射里的東西,但是實際情況卻是任何Map的使用者都能使用clear()方法清空Map中的映射。
接下來用代碼演示一下。假如你的應用程序需要一個能存儲Sensor的Map,那一般可以這么寫:
Map sensors = new HashMap();
當代碼的其它部分需要訪問這些sensor時,可以這樣做:
Sensor s = (Sensor) sensors.get(sensorId);
這種獲取元素的方式雖然可行,但是調用端承擔了從Map對象中取得對象并將其轉換成正確類型的職責,并非整潔的代碼。可以通過使用泛型,讓代碼變整潔:
Map<Sensor> sensors = new HashMap<Sensor>();
...省略put操作
Sensor s = sensors.get(sensorId);
在系統(tǒng)中無節(jié)制的傳遞Map<Sensor>對象的引用,意味著如果Map提供的接口被修改時,有非常非常多的地方需要跟著改動。例如,Java 5加入對泛型的支持時,Map接口的確發(fā)生了變動。
所以在本例中,使用Map的更整潔的方式大致如下:
sensors的用戶不關心是不是使用了泛型,把類型轉換等操作提取到Sensor類中作為一個方法,這樣在接口發(fā)生改變時,我們只需要修改這一個方法即可。
public class Sensors {
private Map sensors = new HashMap;
public Sensor getById(String id) {
return (Sensor) sensors.get(id);
}
}
小總結一下:并不建議總是以這種方式封裝Map,建議不要將Map(或者邊界上的其它接口)在系統(tǒng)中傳遞。如果你使用類似Map這樣的邊界接口,就把它保留在類中。避免從公共API中返回邊界接口,或者將邊界接口作為參數(shù)傳遞給公共方法。
二、使用尚不存在的代碼
協(xié)同開發(fā)過程中經常會碰到代碼調用的API還沒有處理好的情況,在這中情況下我們定義好我們的接口和方法。一旦API被定義出來,可以使用適配器模式來進行銜接。Adapter封裝了與API的互動,也提供了一個當API發(fā)生變動時唯一需要改動的地方。
讀者可以自行了解一下Adapter設計模式。記住這種應用場景。
三、整潔的邊界
在我們的代碼中應該盡量少的了解第三方代碼中的特定信息,也就需要盡量去封裝好第三方邊界接口??梢韵穹庋bMap那樣,也可以使用適配器模式將我們的接口轉換為第三方的接口。采用這兩種方法,代碼都能更好地與我們溝通,在邊界兩邊推動內部一致的用法。這樣當?shù)谌酱a改動的時候,我們的代碼修改點也會更少。
參考
《代碼整潔之道》