溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java中API設(shè)計的示例分析

發(fā)布時間:2021-09-09 11:32:34 來源:億速云 閱讀:86 作者:小新 欄目:編程語言

這篇文章給大家分享的是有關(guān)Java中API設(shè)計的示例分析的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

前言

了解在設(shè)計 Java API 時應(yīng)該運用的一些 API 設(shè)計實踐。這些實踐通常很有用,而且可確保 API 能在諸如 OSGi 和 Java Platform Module System (JPMS) 之類的模塊化環(huán)境中得到正確使用。有些實踐是規(guī)定性的,有些則是禁止性的。當(dāng)然,其他良好的 API 設(shè)計實踐也同樣適用。

OSGi 環(huán)境提供了一個模塊化運行時,使用 Java 類加載器概念來強制實施類型可見性封裝。每個模塊都將有自己的類加載器,該加載器將連接到其他模塊的類加載器,以共享導(dǎo)出的包并使用導(dǎo)入的包。

Java 9 引入了 JPMS,后者提供了一個模塊化平臺,使用來自 Java 語言規(guī)范的訪問控制概念來強制實施類型可訪問性封裝。每個模塊都定義了哪些包將被導(dǎo)出,從而可供其他模塊訪問。默認(rèn)情況下,JMPS 層中的模塊都位于同一個類加載器中。

一個包可以包含一個 API。這些 API 包的客戶端有兩種角色:API 使用者和 API 提供者。API 使用者使用 API 提供者實現(xiàn)的 API。

在以下設(shè)計實踐中,我們將討論包的公共部分。包的成員和類型不是公共的,或者說是受保護的(即私有或默認(rèn)可訪問的),無法從包的外部訪問它們,所以它們是包的實現(xiàn)細(xì)節(jié)。

Java 包必須是一個有凝聚力、穩(wěn)定的單元

Java 包的設(shè)計必須確保它是一個有凝聚力且穩(wěn)定的單元。在模塊化 Java 中,包是模塊之間共享的實體。一個模塊可以導(dǎo)出一個包,以便其他模塊可以使用這個包。因為包是在模塊之間共享的單元,所以它必須有凝聚力,因為包中的所有類型必須與包的特定用途相關(guān)。

不鼓勵使用混雜的包,比如 java.util,因為這種包中的類型通常是彼此不相關(guān)的。這類沒有凝聚力的包可能導(dǎo)致大量依賴項,因為包的不相關(guān)部分會引用其他不相關(guān)的包,而且對包的某個方面的更改會影響依賴于此包的所有模塊,即使模塊可能并未實際使用該包的被修改部分。

由于包是一個共享單元,所以它的內(nèi)容必須是眾所周知的,而且隨著包在未來版本中的演變,只能以兼容方式更改所包含的 API。這意味著包不能支持 API 超集或子集;例如,可以將 javax.transaction 視為內(nèi)容不穩(wěn)定的包。

包的用戶必須能夠了解包中提供了哪些類型。這也意味著包應(yīng)由單個實體(例如一個 jar 文件)提供,不得跨多個實體進行拆分,因為該包的用戶必須知道整個包的存在。

此外,該包必須以兼容方式實現(xiàn)在未來版本中的演變。因此,應(yīng)該對包進行版本控制,而且其版本號必須根據(jù)語義版本控制規(guī)則進行演變。

但最近我意識到,針對包的主版本更改的語義版本控制建議是錯誤的。包的演變必須是功能的增加。在語義版本控制中,這會增加次要版本。

當(dāng)刪除功能時,您會對包執(zhí)行不兼容的更改,而不是增加主版本, 您必須改用一個新的包名,而讓原始包保持可兼容。在對包執(zhí)行不兼容的更改時,應(yīng)該改用新的包名,而不是更改主版本。

最小化包耦合

一個包中的類型可以引用其他包中的類型,例如,某個方法的參數(shù)類型和返回類型,以及某個字段的類型。這種包間耦合會在包上造成所謂的使用限制。這意味著 API 使用者必須使用 API 提供者引用的相同包,以便他們都能了解所引用的類型。

一般而言,我們希望最小化這種包耦合,以便最小化包上的使用限制。這可以簡化 OSGi 環(huán)境中的連接解析,并最大限度地減少依賴扇出,從而簡化部署。

接口優(yōu)先于類

對于 API,接口優(yōu)先于類。這是一種相當(dāng)常見的 API 設(shè)計實踐,對模塊化 Java 也很重要。接口的使用提高了實現(xiàn)的自由度,還支持多種實現(xiàn)。

接口對于讓 API 使用者與 API 提供者分離至關(guān)重要。無論是實現(xiàn)接口的 API 提供者,還是調(diào)用接口上的方法的 API 使用者,都允許使用包含 API 接口的包。

通過這種方式,API 使用者不會直接依賴于 API 提供者。它們都只依賴于 API 包。

除接口外,抽象類有時也是一種有效的設(shè)計選擇,但接口通常是首選,尤其是考慮到接口的最新改進允許添加 default 方法。

最后,API 通常需要許多小的具體類,比如事件類型和異常類型。這沒什么問題,但這些類型通常應(yīng)該是不可變的且不被 API 使用者用來創(chuàng)建子類。

避免靜態(tài)

應(yīng)在 API 中要避免靜態(tài)。類型不應(yīng)包含靜態(tài)成員。靜態(tài)工廠也應(yīng)該避免。實例的創(chuàng)建應(yīng)與 API 分離。例如,API 使用者應(yīng)通過依賴注入或者 OSGi 服務(wù)注冊表或 jPMS 中的 java.util.ServiceLoader 之類的對象注冊表來接收 API 類型的對象實例。

避免靜態(tài)也是一種創(chuàng)建可測試的 API 的良好實踐,因為靜態(tài)不容易模仿。

單例

API 設(shè)計中有時存在單例對象。但是,不應(yīng)通過靜態(tài) getInstance 方法或靜態(tài)字段等靜態(tài)對象來訪問單例對象。當(dāng)需要使用單例對象時,該對象應(yīng)由 API 定義為單例,并通過上文提到的依賴注入或?qū)ο笞员硖峁┙o API 使用者。

避免類加載器假設(shè)

API 通常具有可擴展性機制,API 使用者可在其中提供 API 提供者必須加載的類名。然后,API 提供者必須使用 Class.forName(可能使用線程上下文類加載器)來加載該類。這種機制會假定從 API 提供者(或線程上下文類加載器)到 API 使用者的類可見性。

API 設(shè)計必須避免類加載器假設(shè)。模塊化的一個主要特點是類型封裝。一個模塊(例如 API 提供者)不能對另一個模塊(例如 API 使用者)的實現(xiàn)細(xì)節(jié)具有可見性/可訪問性。

API 設(shè)計必須避免在 API 使用者與 API 提供者之間傳遞類名,而且必須避免與類加載器分層結(jié)構(gòu)和類型可視性/可訪問性有關(guān)的假設(shè)。

為了提供可擴展性模型,API 設(shè)計應(yīng)讓 API 使用者將類對象,或者最好將實例對象,傳遞給 API 提供者。這可以通過 API 中的一個方法或 OSGi 服務(wù)注冊表之類的對象注冊表來完成。請參見白板模式。

在 JPMS 模塊中不使用 java.util.ServiceLoader 類時,也會受到類加載器假設(shè)的影響,因為它假設(shè)所有提供者都對線程上下文類加載器或所提供的類加載器可見。

此假設(shè)在模塊化環(huán)境中通常是不成立的,但 JPMS 允許通過模塊聲明來聲明模塊提供或使用了一個 ServiceLoader 托管服務(wù)。

不進行持久性假設(shè)

許多 API 設(shè)計都假設(shè)只有一個構(gòu)造階段,對象在該階段被實例化并添加到 API,但是忽略了動態(tài)系統(tǒng)中可能發(fā)生的解構(gòu)階段。

API 設(shè)計應(yīng)考慮到,對象可以添加,也可以刪除。例如,大多數(shù)監(jiān)聽器 API 都允許添加和刪除監(jiān)聽器。但是,許多 API 設(shè)計僅假設(shè)對象可以添加,并且從未刪除這些對象。例如,許多依賴注入系統(tǒng)無法撤銷注入的對象。

在 OSGi 環(huán)境中,可以添加和刪除模塊,因此能夠兼顧到此類動態(tài)的 API 設(shè)計非常重要。OSGi Declarative Services 規(guī)范為OSGi 定義了一個依賴注入模型,該模型支持這些動態(tài)操作,包括撤銷注入對象。

明確規(guī)定 API 使用者和 API 提供者的類型角色

如前言中所述,API 包的客戶端有兩種角色:API 使用者和 API 提供者。API 使用者使用 API,而 API 提供者實現(xiàn) API。對于 API 中的接口(和抽象類)類型,API 設(shè)計一定要明確規(guī)定哪些類型僅由 API 提供者實現(xiàn),哪些類型可由 API 使用者實現(xiàn)。例如,監(jiān)聽器接口通常由 API 使用者實現(xiàn),而實例被傳遞給 API 提供者。

API 提供者對 API 使用者和 API 提供者實現(xiàn)的類型更改都很敏感。提供者必須實現(xiàn) API 提供者類型中的任何新更改,而且必須了解且可能調(diào)用 API 使用者類型中的任何新更改。

API 使用者通??梢院雎?API 提供者類型的(兼容)更改,除非它希望通過更改來調(diào)用新功能。但是 API 使用者對 API 使用者類型的更改很敏感,而且可能需要修改才能實現(xiàn)新功能。

例如,在 javax.servlet 包中,ServletContext 類型由 API 提供者(比如 servlet 容器)實現(xiàn)。向 ServletContext 添加新方法需要更新所有 API 提供者來實現(xiàn)新方法,但 API 使用者無需執(zhí)行更改,除非他們希望調(diào)用該新方法。

但是,Servlet 類型由 API 使用者實現(xiàn),向 Servlet 添加新方法需要修改所有 API 使用者來實現(xiàn)新方法,還需要修改所有 API 提供者來使用該新方法。因此,ServletContext 類型有一個 API 提供者角色,Servlet 類型有一個 API 使用者角色。

由于通常有許多 API 使用者和很少的 API 提供者,所以在考慮更改 API 使用者類型時,必須非常謹(jǐn)慎地執(zhí)行 API 演變,而 API 提供者類型更改的要求更加寬松。

這是因為,您只需要更改少數(shù) API 提供者來支持更新的 API,但您不希望在更新 API 時需要更改許多現(xiàn)有的 API 使用者。僅當(dāng) API 使用者希望使用新 API 時,API 使用者才需要執(zhí)行更改。

OSGi Alliance 定義了文檔注釋、ProviderType 和 ConsumerType 來標(biāo)記 API 包中的類型角色。這些注釋包含在 osgi.annotation jar 中供您的 API 使用。

感謝各位的閱讀!關(guān)于“Java中API設(shè)計的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI