使用默认方法进行接口演化–第二部分:接口
引入了默認方法以啟用接口演進。 如果向后兼容是不可替代的,則僅限于向接口添加新方法(這是它們在JDK中的唯一用法)。 但是,如果希望客戶端更新其代碼,則可以使用默認方法逐步演化接口而不會引起編譯錯誤,從而使客戶端有時間將其代碼更新為新版本的接口。
這個小型系列的第一部分說明了默認實現如何允許在不破壞客戶端代碼的情況下添加,替換和刪除方法。 我愚蠢地宣布:“以后的文章將探討替換整個接口的方法”,同時也不會破壞客戶端代碼。
好吧,您現在正在閱讀這篇文章,不幸的摘要是:
我無法使其工作。
為什么? 泛型。
到底為什么 你真的想知道嗎 好吧,那么請繼續閱讀,但是這篇文章的其余部分實際上只是對我如何成為障礙的描述,因此不要期望太多。 (大激勵,是嗎?)
總覽
首先,在描述我嘗試過的方法和失敗的方法之前,先定義要解決的問題。
問題陳述
這是我們想要做的:
假設您的代碼庫包含一個接口,您的客戶端可以用所有可以想象的方式使用該接口:它們具有自己的實現,使用其實例調用您的代碼,并且您的代碼返回此類實例,當然他們將其用作參數的類型和返回值。
現在,您要實質性地更改接口:以無法用對單個方法的更改來表示的方式對其進行重命名,移動或修改。 (但是從提供一個版本到另一個版本的角度來看,這兩個接口仍然是等效的。)
您可以這樣做,發布具有更改的新版本,并告訴您的客戶端修復其導致的編譯錯誤。 如果他們的代碼與您的代碼高度耦合,那么他們可能必須在單獨的分支中執行此操作以花一些時間在代碼上,但這就是生活,對嗎? 不過,您真是個好人,因此,與其要求費時的日子,不如讓他們有機會隨著時間的推移(例如,直到下一個版本)逐漸更改其代碼,而沒有任何編譯錯誤。
(請注意,這是接下來所有內容的主要要求。我首先忽略了這是否是個好主意。我只是想看看能達到的程度。)
我認為甚至有機會實現這一目標的唯一方法是定義一個過渡階段,在該階段中,新舊版本的接口都將共存。 因此,我們真正需要的是一種通用的分步方法,即如何將實現,調用者和聲明從一個接口轉移到另一個接口。
想法
在宣布這篇文章時,我對它的工作方式有一個具體的想法。 基本上與我在方法中使用的方法相同。
不斷發展的接口方法
使用默認方法添加,替換或刪除接口的單個??方法非常簡單,通常包括三個步驟(在某些情況下更少):
- 新版本:庫的新版本發布,其中界面定義是過渡性的,并結合了舊的和新的所需輪廓。 默認方法可確保所有外部實現和調用仍然有效,并且在更新時不會出現編譯錯誤。
- 過渡:然后客戶有時間從舊大綱過渡到新大綱。 同樣,默認方法可確保適應的外部實現和調用有效,并且可以進行更改而不會編譯錯誤。
- 新版本:在新版本中,該庫刪除了舊輪廓的殘差。 鑒于客戶端明智地利用了自己的時間并進行了必要的更改,因此發布新版本不會導致編譯錯誤。
如果您對這些步驟的詳細說明感興趣,可以閱讀我的早期文章 。
改進界面
在這種情況下,這種方法似乎也很有意義,所以我坐下來進行演示。
如果整個接口發生更改,則要復雜一些,因為在方法僅具有調用者和實現的地方,該接口也是一種類型,即可以在聲明中使用。 這使得必須區分三種使用接口的方式:
- 內部使用 ,您在其中擁有實現和使用接口的代碼
- 已發布的使用 ,您擁有實現,但客戶端調用了代碼
- 外部使用 ,其中客戶端擁有實現和使用接口的代碼
起作用的部分采用與演化方法相同的方法:
- 新版本:使用新界面發布新版本,以擴展舊版本。 讓所有內部代碼實現并使用新接口。 所有已發布的代碼將使用舊接口聲明參數類型,并使用新接口返回類型。 如果必須轉換實例,則可以使用適配器來完成。 現在忽略參數化類型,此更改將不會導致客戶端代碼中的編譯錯誤。
- 過渡:版本發布后,客戶端更改其代碼。 從舊接口的實現(更改為實現新接口的實現)和已發布的代碼返回的實例開始,他們可以開始聲明新類型的實例,更新將它們傳遞給它們的方法的參數類型,等等。上。 如有必要,可以暫時使用適配器通過新接口與舊實例進行交互。
- 新版本:發布一個刪除舊界面的版本。
與不斷發展的方法相同,新接口中的默認實現允許客戶端代碼停止明確實現舊接口,從而可以在第二個版本中將其刪除。 另外,舊接口上的便捷asNew()方法可以調用適配器以使其自身適應新接口。
我掩蓋了一些細節,但我希望你相信我,這是可行的。 現在讓我們回到泛型…
障礙
提出的方法中的關鍵部分是已發布的代碼。 它由您的客??戶調用,因此第一個發行版必須以兼容的方式對其進行更改。 并且由于所有內部代碼都需要新接口,因此它必須邁出從Old到New的一步。
沒有泛型,它可能看起來像這樣:
在已發布的代碼中將“舊”轉換為“新”
// in version 0 public Old doSomething(Old o) {// 'callToInternalCode' requires an 'Old'callToInternalCode(o);return o; }// in version 1 the method still accepts 'Old' but returns 'New' public New doSomething(Old o) {// 'callToInternalCode' now requires a 'New'New n = o.asNew();callToInternalCode(n);return n; }好的,到目前為止很好。 現在,讓我們看看泛型的外觀。
在已發布的代碼中將“舊”轉換為“新” –泛型
// in version 0 public Container<Old> doSomething(Container<Old> o) {// 'callToInternalCode' requires a 'Container<Old>'callToInternalCode(o);return o; }// in version 1 // doesn't work because it breaks assignments of the return value public Container<New> doSomething(Container<Old> o) {// 'callToInternalCode' requires a 'Container<New>'// but we can not hand an adapted version to 'callToInternalCode'// instead we must create a new containerNew nInstance = o.get().asNew();Container<New> n = Container.of(nInstance);callToInternalCode(n);return n; }因此,使用已發布的代碼層從舊界面適應新界面通常不起作用,原因至少有兩個:
- 由于Java中泛型的不變性,返回值的所有分配都將中斷:
不變性打破分配 Container<Old> old = // ... // works in version 0; breaks in version 1 Container<Old> o = published.doSomething(old); - 不能將同一Container實例從已發布傳遞到內部代碼。 這導致兩個問題:
- 創建一個新容器可能很困難或不可能。
該死的…
發布時間由交通運輸的華盛頓州部門在CC-BY-NC-ND 2.0 。
從一開始,我就感到仿制藥會很麻煩-回想起來,這實際上很明顯。 當涉及類型時,泛型怎么可能不是問題。 因此,也許我應該先嘗試解決難題。
可能繞行
在將我的頭撞在墻上一段時間之后,我仍然沒有找到解決此問題的通用方法。 但是我想出了一些可能有助于解決特殊情況的想法。
通配符
您可以檢查已發布的內部代碼是否充分利用了通配符(請記住PECS )。 您也可以建議客戶如何使用它們。
根據情況,這可能會產生解決方案。
專用接口,類,實例
根據具體的代碼,可以提供使用舊接口的已發布接口,類或實例的新版本。 如果可以通過讓客戶端選擇使用依賴于舊接口的接口,類或實例還是依賴于新接口的接口,類或實例的方式來處理代碼,則各個實現不必進行過渡。
但這可能會將舊界面推回內部代碼,而內部代碼剛剛更新為僅使用新接口。 聽起來也不好。
容器適配器
您可以在已發布的代碼中為與舊接口一起使用的容器提供適配器。 這實際上將允許您在這些容器上調用asNew() 。
(出于不相關的原因,我目前正在為某些JDK集合進行此類轉換。下一個版本的LibFX將包含它們;如果您好奇,可以在GitHub上查看演示。)
算了!
這一切又是為了什么? 為了防止客戶創建分支,在將所有內容合并回master之前花一些時間在那里修復問題嗎? 算了!
在這一點上,這是我對此的看法。 只要您只處理單個方法,接口的演化就很平穩,但是當您要替換整個接口時,這似乎會很痛苦。 因此,除非有充分的理由介紹所有這些復雜性,否則我將以困難的方式進行操作,然后讓客戶對其進行分類。 還是根本不做。
而且,如果您只是重命名或移動界面,無論如何,大部分甚至全部工作都可以通過簡單的搜索替換來完成。
反射
我們重申了如何將默認方法用于發布,過渡和發布三部分的界面演化。 盡管這對單個方法有效,但我們發現它無法替換整個接口。 主要問題是參數類型的不變性使我們無法將已發布的代碼用作適應層。
即使我們看到了一些解決該問題的方法,也沒有一個好的解決方案脫穎而出。 最后,看起來不值得麻煩。
我有事嗎 還是整個想法愚蠢? 為什么不發表評論!
翻譯自: https://www.javacodegeeks.com/2015/04/interface-evolution-with-default-methods-part-ii-interfaces.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的使用默认方法进行接口演化–第二部分:接口的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Hostinger vs DonWeb—
- 下一篇: 霍廷格对贾斯特霍斯特-哪一个更好?[20