當遇到深層巢狀程式碼,如for,if,lambda表示式或內部類及這些程式碼的組合,這時我們可以通過Java 8的語法特性來進行優化。
下面的程式碼是一個巢狀迴圈的示例。
public MappedField getMappedField(final String storedName) { for (final MappedField mf : persistenceFields) { for (final String n : mf.getLoadNames()) { if (storedName.equals(n)) { return mf; } } } return null; }
重構1:
巢狀的for/if語句通暢可以通過Java 8中的stream來替代。
Optional<String> found = persistenceFields.stream() .flatMap(mappedField -> mappedField.getLoadNames().stream()) .filter(storedName::equals) .findFirst();
上述重構程式碼會返回Optional,但筆者希望返回mappedField物件,再次改造後的程式碼如下。
persistenceFields.stream() .filter(mappedField -> { for (String name : mappedField.getLoadNames()) { if (storedName.equals(name)) { return true; } } return false; } ) .findFirst()
重構2: 進行更好的封裝
重構1還存在一些問題,我們需要了解mappedField的結構,並通過迴圈遍歷其所有name來找到匹配的name。根據迪米特法則(Law of Demeter ),及命令-不要去詢問法則(Tell, Don’t Ask), 下面程式碼應該由MappedField物件來提供對應的方法來判斷,而不是由呼叫者去了解MappedField結構後去寫邏輯進行判斷。
for (final MappedField mf : persistenceFields) { if (mf.hasName(storedName)) { return mf; } }
因此將上述程式碼提取為MappedField類中獨立的方法,並命名為hasName。如果使用的IDE 是IDEA則可以通過refractor中的extract功能完成提取。
最後呼叫hasName方法來替代迴圈判斷邏輯。
接著通過Idea的refractor 中的move功能將程式碼移動到目標類位置。
接著通過stream來重構hasName方法,hasName方法變更為下面的形式。
public Boolean hasName(String storedName) { return getLoadNames().stream() .anyMatch(storedName::equals); }
經過上述步驟最終重構後的程式碼為。
public MappedField getMappedField(final String storedName) { return persistenceFields.stream() .filter(mf -> mf.hasName(storedName)) .findFirst() .orElse(null); }
如需要返回Optional包裝的物件則需要去掉orElse。
public Optional<MappedField> getMappedField(final String storedName) { return persistenceFields.stream() .filter(mf -> mf.hasName(storedName)) .findFirst(); }
總結
這類程式碼特徵通常為:
- 存在深層的迴圈或條件判斷巢狀。
- 需要通過多個getter方法來訪問物件內部資料。
重構方法:
考慮tell don’t ask原則,提供專用的方法供外部呼叫訪問資料,而不是通過使用者經過多次訪問去獲取物件資料。並通過stream提供的操作來完成重構。
————END————