您真的需要instanceof吗?
使用instanceof是一種代碼味道。 我認為我們可能對此表示同意。 每當我看到這樣的構造時,我肯定會出現問題。 也許有人只是在進行更改時沒有注意到問題? 也許有一個主意,但是它太復雜了,以至于需要太多的精力或時間才能讓開發人員決定不做呢? 也許只是懶惰? 誰知道。 事實仍然是代碼演變成這種狀態,我們必須與之合作。
或者也許我們可以做些什么? 有什么可以打開我們的擴展代碼嗎?
今天,我想向您展示如何實現這一目標。 但是首先,讓我解釋一下為什么這個instanceof根本是個問題。
看一下代碼
今天我們將討論以下代碼:
public class ChangeProcessingHandler {public CodeDelta triggerProcessingChangeOf(Code code, Change change) {verifyChangeOf(code, change);if (change instanceof Refactoring) {return processRefactoring(code, (Refactoring) change);} else if (change instanceof Improvement) {return processImprovement(code, (Improvement) change);} else if (change instanceof Growth) {return processGrowth(code, (Growth) change);} else {throw new UnsuportedChangeException();}}// some more code }我們將嘗試對其進行改進。
我試圖使這段代碼具有描述性,但是讓我簡要地總結一下。 根據Change接口實現的特定類型,我們選擇一種準確的處理方式。 如果找不到匹配的類型,我們將拋出一個異常。
現在,讓我們看一下這段代碼有什么問題。
接口及其實現?
當您查看方法的聲明時,您能怎么說呢? 確實需要兩個輸入參數。 它給我們什么樣的信息? 我們知道依賴關系,并基于它們的API,我們知道如何在方法主體中與傳遞的對象進行交互。
在給定的例子中是真的嗎? 不幸的是沒有。 我們正在傳遞一個Change實例,并且我們希望方法的主體將取決于其接口。 但是在內部,我們將實例轉換為特定類型,這導致依賴性增加。
這本身不是一個好的設計決策,但更糟糕的是–我們在幕后增加了這個數字。 直到您不閱讀方法的主體,您都不會知道。
缺乏知識遠比依賴數量嚴重得多。
新類型不是那么容易添加
假設您必須添加Change接口的新實現。 會發生什么? 好吧,什么都沒有。 您將添加類定義并對其進行測試。 您將運行所有測試。 如果至少有一個組件或系統測試能夠通過新引入的Change接口實現達到所提供的代碼并且失敗,您將很幸運。
當沒有這樣的測試時,問題就開始了,您甚至都不知道應該改變某個地方以適應新功能。
一切都會編譯,您將一直工作到……
為什么?
您在代碼中注意到了這個不錯的UnsupportedChangeException嗎? 老實說,它在那里只是因為設計錯誤。
我們擁有它有兩個原因:
- 沒有它,代碼將無法編譯。 當然,如果方法為空,我們可以跳過它,但是在我們的示例中,我們必須返回或拋出一些東西。 我們可以用其他方法代替last if-else,但這不是我們想要做的。
- 它使我們無法添加新類型,而忘記在其中添加對新引入功能的支持。 假設至少有一種測試會在這種情況下失敗。
為什么我稱其為錯誤的設計? 好吧,使用異常來表示支持新功能的需求是對異常的濫用。 我還相信,如果我們的代碼通過不編譯來發出信號,那會更好。 這對我來說很有意義,而且肯定會提供更快的反饋。
來訪者進行搶救!
訪問者允許我們添加其他功能,其實現取決于對象的特定類型。 它允許使用接口的方法。 因此,我們可以避免自己檢索有關特定接口的實現的信息。
首先,我們需要使檢索有關對象類型的信息成為可能。 為此,我們必須在接口中添加一種方法,該方法將允許我們傳遞訪問者:
public interface Change {void accept(Visitator visitator); }實現接口的每個對象的實現非常簡單:
public class Refactoring implements Change {@Overridepublic void accept(Visitator visitator) {visitator.visit(this);}// some code }通過查看調用方法visit()的行可以觀察到什么? 在這里可以檢索有關類型的信息。 不需要instanceof,不需要強制轉換。 這是我們在更好的設計支持下免費獲得的東西。
這時,您可能知道Visitor的界面如下所示:
public interface Visitator {void visit(Refactoring refactoring);void visit(Improvement improvement);void visit(Growth growth); }不是那么復雜,不是嗎?
之后,我們必須從ChangeProcessingHandler類中提取一些代碼到實現Visitor接口的類中:
public class ChangeProcessor implements Visitator {private final Code code;public ChangeProcessor(Code code) {this.code = code;}@Overridepublic void visit(Refactoring refactoring) {// some code}@Overridepublic void visit(Improvement improvement) {// some code}@Overridepublic void visit(Growth growth) {// some code} }當然,我們必須在正確的地方使用它:
public class ChangeProcessingHandlerRefactored {public void triggerProcessingChangeOf(Code code, Change change) {verifyChangeOf(code, change);change.accept(new ChangeProcessor(code));} }是不是更好?
好的,所以我們更改了原始代碼。 現在讓我解釋一下我們獲得了什么。
- 我們剛剛擺脫了一個例外。 不再需要它了,因為對新引入的實現的必要支持將通過非編譯代碼發出信號。
- 快速反饋是使用接口的結果,該接口將告訴我們要完全支持所有功能還需要實現什么。
- 單一責任原則之所以起作用,是因為“訪客”界面的每個特定實現僅負責一個功能。
- 設計是面向行為的(接口),而不是面向實現的(instanceof + cast)。 這樣,我們隱藏了實現細節。
- 設計對擴展開放。 引入易于實現的新功能真的很容易,這些功能針對特定對象而有所不同。
它不是那么完美
每個設計都是一個權衡。 您得到了一些東西,但這是有代價的。
我在上段中列出了好處,那么成本呢?
-  這么多的對象 
 可能有人說這是使用任何設計模式的明顯結果,我會說是的。 但是,這并不能改變以下事實:隨著對象數量的增加,在對象之間導航變得更加困難。
 將所有內容都放在一個對象中可能是一個問題,但名稱不正確或雜亂無章的類可能會導致混亂。
-  復雜 
 所有這些對象都需要一個名稱,如果這些對象與域相關,那就太好了。 在這種情況下,我們最終會對我們的應用程序有了更好的了解。 但這并非總是如此。
 同樣,我們在命名新引入的類時也必須非常小心。 所有這些都必須以不言自明的方式命名。 這并不像某些人想象的那么容易。
-  我的(受限)上下文在哪里? 
 訪客可以幫助解決與示例中出現的問題類似的問題。 但是,如果有很多類似的地方,您必須意識到每個訪問者都在某種程度上將對象的行為放入另一個對象。 得墨meter耳定律呢? 那告訴泰勒,不要問嗎?
 在使用訪客解決instanceof問題之前,您應該問自己這個功能是否不是對象本身的一部分? 一些開發人員向我解釋這是擁有小對象的一種方式。 好吧,對我而言,這種解釋證明了我們應該考慮綁定上下文 。 對象仍然很小,它們的行為也不會泄漏給外部類。
就這樣,伙計們
今天就這些。 我希望您發現重新設計的想法很有用,并且在閱讀本文之后,您的代碼中的異味肯定會受到威脅。 與往常一樣,我鼓勵您發表評論并分享您的觀點和經驗。 也許您更多地了解與此類變更相關的收益/問題。
翻譯自: https://www.javacodegeeks.com/2016/10/really-need-instanceof.html
總結
以上是生活随笔為你收集整理的您真的需要instanceof吗?的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 马斯克传记即将出版:曾被劝告不要疏远广告
- 下一篇: qr码生成_从Java程序生成QR码图像
