Java的API設計實踐

銀河1號發表於2019-01-19

Introduction

瞭解在設計Java API時應該應用的一些API設計實踐。通常,這些實踐很有用,並確保API可以在模組化環境中正確使用,例如OSGi和Java平臺模組系統(JPMS)。有些做法是規定性的,有些則是禁止性的。當然,其他良好的API設計實踐也適用。

OSGi環境使用Java類載入器概念提供模組化執行時強制型別可見性(visibility)的封裝。每個模組都有自己的類載入器,它會被連線到其他模組的類載入器,以此來共享匯出的包並使用匯入的包。

Java 9引入了JPMS,它是一個模組化平臺,使用了Java語言規範中的access control概念來強制執行型別的可達性(accessibility)的封裝。每個模組定義匯出哪些包,因此可由其他模組訪問。預設情況下,JMPS層中的模組都駐留在同一個類載入器中。

包可以包含API。API包有兩種角色:API consumers and API providers。

在以下設計實踐中,我們將討論包的公共部分。程式包中非public或非protected的成員和型別,在程式包之外是不可訪問的,因此它們是程式包的實現細節。

Java包必須是一個內聚,穩定的單元

必須設計Java包以確保它是一個內聚、穩定的單元。在模組化Java中,包是模組之間的共享實體。一個模組可以匯出包,以便其他模組可以使用該包。由於包是模組之間共享的單元,因此包必須具有內聚性,因為包中的所有型別都必須與包的特定用途相關。像java.util這樣的包是不鼓勵的,因為這種包中的型別通常彼此沒有關係。這樣的非內聚的包可能導致許多依賴性問題,因為包的不相關部分引用其他不相關的包,並且修改包的一個部分會影響依賴這個包的所有模組,即使模組實際上可能不使用被修改的這部分。

由於包是單元共享,因此其內容必須是眾所周知的,並且包含的API僅在相容方式中隨著包在未來版本的發展而變化。這意味著包不能支援API超集或子集;例如,javax.transaction就是一個內容不穩定的包。包的使用者必須能夠知道包中哪些型別是可用的。這也意味著包應該由單個實體(例如,jar檔案)提供,而不是跨多個實體分開,因為包的使用者必須知道整個包的存在。

此外,包必須以一種相容的方式發展。因此,應該對包進行版本控制,並且其版本號必須根據semantic versioning規則進行演變。

但最近我意識到包的主要版本更改的語義版本控制建議是錯誤的。包演變必須是功能的增加。在語義版本控制中,這增加了次要版本。當您刪除功能時,即對包進行不相容的更改,您必須移動到新的包名稱,使原始包仍然相容。要了解為什麼這很重要且必要,請參閱本文Semantic Import Versioning for Go。這兩種情況都適用於在對包進行不相容的更改時轉移到新包名而不是更改主要版本的情況。

包間耦合最小化

包中的型別可以引用其他包中的型別。例如,方法的引數型別和返回型別以及欄位的型別都可能引用其他包的型別。這種包間耦合創造了所謂的包與包之間的uses關係。這意味著API consumer必須使用與API provider相同的引用包,以便他們理解引用的型別。

通常,我們希望最小化包間耦合以最小化對包的使用約束。這簡化了OSGi環境中的佈線解析度,並最大限度地減少了依賴扇出,簡化了部署(This simplifies wiring resolution in the OSGi environment and minimizes dependency fan-out simplifying deployment)。

介面比類更受歡迎

對於API,介面比類更受歡迎。這是一種相當常見的API設計實踐,對模組化Java也很重要。對介面的實現很自由,一個介面可以有多個實現。介面對於將API consumer與API provider分離是很重要的。它使得一個包含API的包,既可以被API consumer使用,也可以被API provider使用。通過這種方式,API consumer與API provider沒有直接的依賴關係。它們都只依賴於API包。

抽象類有時是一種有效的設計選擇,但通常介面是首選,特別是考慮到最近介面新增了default methods這一改進.

最後,API通常需要許多小的具體類,例如事件型別和異常型別。這很好,但型別通常應該是不可變的,不適合API使用者進行子類化。

避免 statics

應該在API中避免使用靜態。型別不應該有靜態成員。應避免使用靜態工廠。應該將例項建立與API分離。例如,API consumer應該通過依賴注入或物件登錄檔(如OSGi服務登錄檔或者JPMS的java.util.ServiceLoader)來接收API型別的物件例項.

避免靜態也是製作可測試API的好方法,因為靜態不容易被模擬。

Singletons

有時在API設計中有單例物件。但是,對單例物件的訪問不應該像靜態一樣通過靜態getInstance方法或靜態欄位來訪問。當需要單個物件時,該物件應該由API定義為單例,並通過依賴注入或如上所述的物件登錄檔提供給API consumer。

避免類載入器假設

API通常具有可擴充套件性機制,API consumer可以提供API provider必須載入的類的名稱。API provider然後必須使用Class.forName(可能使用的是執行緒上下文類載入器)來載入類。這種機制保證了從API provider(或執行緒上下文類載入器)到API consumer的類可見性。 API設計必須避免類載入器假設。模組化的一個要點是型別封裝。一個模組(例如,API provider)必須不具有對另一個模組(例如,API consumer)的實現細節的可見性/可訪問性。

API設計必須避免在API consumer和API provider之間傳遞類名,並且必須避免關於類載入器層次結構和型別可見性/可訪問性的假設。為了提供可擴充套件性模型,API設計應該讓API consumer將類物件或更好的例項物件傳遞給API provider。這可以通過API中的方法或通過物件登錄檔(例如OSGi服務登錄檔)來完成。見whiteboard pattern.

java.util.ServiceLoader類,當在JPMS模組中沒有使用時,也會受到類載入器假設的影響,因為它假定所有提供者都可以從執行緒上下文類載入器或提供的類載入器中看到。雖然JPMS允許模組宣告宣告模組提供或使用ServiceLoader managed service,但在模組化環境中通常不會出現這種假設 .

不要假設永久性

許多API設計只假設一個構造階段,其中物件被例項化並新增到API中,但忽略了在動態系統中可能發生的破壞階段。 API設計應該考慮物件可以來,他們可以去。例如,大多數listener API允許新增和刪除listener。但是許多API設計只假設新增了物件並且從未刪除過。例如,許多依賴注入系統無法撤回注入的物件。

在OSGi環境中,可以新增和刪除模組,因此可以適應這種動態的API設計非常重要。該OSGi Declarative Services specification定義了OSGi的依賴注入模型,它支援這些動態,包括注入物件的撤銷。

針對provider和consumer劃分API

如簡介中所述,API包的客戶端有兩個角色:API consumer和API provider。 API consumer使用API,API provider實現API。對於API中的介面(和抽象類)型別,重要的是API設計清楚地記錄哪些型別僅由API provider實現,而API consumer不可以實現。為了方便記憶,我們把API provider需要實現的部分記為P,把API consumer需要實現的部分記為C。例如,偵聽器介面通常由API consumer實現,並且例項傳遞給API provider。

API provider對API 中P部分和C部分更改都很敏感。API provider必須實現API中P部分的型別的任何新更改,並且必須瞭解C部分的任何新更改。 API consumer通常可以忽略API中P部分的更改,除非它想要更改以呼叫新函式。但API consumer對API中C部分的更改很敏感,可能需要修改才能實現新功能。例如,在javax.servlet package, ServletContext由API provider(如servlet容器)實現。為ServletContext新增新方法將要求更新所有API provider以實現新方法,但API consumer不必更改,除非他們希望呼叫新方法。然而Servlet由API consumer實現,為Servlet新增新方法將要求修改所有API consumer以實現新方法,並且還需要修改所有API provider以使用新方法。就這樣ServletContext類似於API的P部分,Servlet類似於API中C部分。

由於通常有許多API consumer和很少的API provider,因此在考慮更改API 中C部分時,API演變必須非常小心。這是因為,您需要更改少數API provider以支援更新的API,但您不希望在更新API時更改許多現有API consumer。 API consumer只需要在API consumer想要利用新API時進行更改。

Conclusion

下次設計API時,請考慮這些API設計實踐。然後,您的API將可用於模組化Java和非模組化Java環境。

英文原文:https://developer.ibm.com/articles/api-design-practices-for-java


更多文章歡迎訪問 http://www.apexyun.com/

聯絡郵箱:public@space-explore.com

(未經同意,請勿轉載)


相關文章