Java Builder模式下相似代码的清洁方法提取咨询
提取相似代码为可复用方法的方案
问题场景
两段代码逻辑高度一致,仅存在两处差异:
- 调用
retrieveSimilarParts时传入的第一个参数不同(extendedSimilarParts/similarParts) - 构建
PartResultDto时调用的Builder方法不同(.extendedSimilarParts(results)/.similarParts(results))
原代码片段:
第一段代码
return search.stream().map(s -> { List<PartDto> results = retrieveSimilarParts(extendedSimilarParts, s.getVdi4081()); return CollectionUtils.isNullOrEmpty(results) ? null : PartResultDto.builder() .identifier(s.getIdentifier()) .extendedSimilarParts(results).build(); }).filter(Objects::nonNull).collect(Collectors.toList());
第二段代码
return search.stream().map(s -> { List<PartDto> results = retrieveSimilarParts(similarParts, s.getVdi4081()); return CollectionUtils.isNullOrEmpty(results) ? null : PartResultDto.builder() .identifier(s.getIdentifier()) .similarParts(results).build(); }).filter(Objects::nonNull).collect(Collectors.toList());
解决方案
可以通过封装通用逻辑+传入差异点的方式实现代码复用,以下是几种简洁的实现方式:
方式一:使用BiConsumer传递Builder操作
定义一个通用方法,把检索参数和Builder的设置动作作为参数传入,核心逻辑只写一次:
private List<PartResultDto> processSimilarParts(List<SearchItem> search, String retrievalParam, BiConsumer<PartResultDto.Builder, List<PartDto>> partsSetter) { return search.stream().map(s -> { List<PartDto> results = retrieveSimilarParts(retrievalParam, s.getVdi4081()); if (CollectionUtils.isNullOrEmpty(results)) { return null; } PartResultDto.Builder builder = PartResultDto.builder() .identifier(s.getIdentifier()); partsSetter.accept(builder, results); return builder.build(); }).filter(Objects::nonNull).collect(Collectors.toList()); }
调用时只需传入对应差异参数即可:
- 对应原第一段代码的调用:
processSimilarParts(search, extendedSimilarParts, (builder, results) -> builder.extendedSimilarParts(results));
- 对应原第二段代码的调用:
processSimilarParts(search, similarParts, (builder, results) -> builder.similarParts(results));
方式二:自定义函数式接口(语义更清晰)
如果觉得BiConsumer的语义不够明确,可以自定义一个函数式接口来描述Builder的设置动作:
@FunctionalInterface interface PartsBuilderSetter { void setParts(PartResultDto.Builder builder, List<PartDto> parts); }
通用方法调整为使用自定义接口:
private List<PartResultDto> processSimilarParts(List<SearchItem> search, String retrievalParam, PartsBuilderSetter partsSetter) { return search.stream().map(s -> { List<PartDto> results = retrieveSimilarParts(retrievalParam, s.getVdi4081()); if (CollectionUtils.isNullOrEmpty(results)) { return null; } PartResultDto.Builder builder = PartResultDto.builder() .identifier(s.getIdentifier()); partsSetter.setParts(builder, results); return builder.build(); }).filter(Objects::nonNull).collect(Collectors.toList()); }
调用方式和之前一致,只是接口名称更具语义:
// 对应原第一段代码 processSimilarParts(search, extendedSimilarParts, (builder, results) -> builder.extendedSimilarParts(results)); // 对应原第二段代码 processSimilarParts(search, similarParts, (builder, results) -> builder.similarParts(results));
方式三:拆分细粒度逻辑(可选)
如果希望代码分层更清晰,可以把map中的转换逻辑单独提取为一个方法:
private PartResultDto mapToPartResult(SearchItem s, String retrievalParam, BiConsumer<PartResultDto.Builder, List<PartDto>> partsSetter) { List<PartDto> results = retrieveSimilarParts(retrievalParam, s.getVdi4081()); if (CollectionUtils.isNullOrEmpty(results)) { return null; } PartResultDto.Builder builder = PartResultDto.builder().identifier(s.getIdentifier()); partsSetter.accept(builder, results); return builder.build(); } // 通用方法简化为: private List<PartResultDto> processSimilarParts(List<SearchItem> search, String retrievalParam, BiConsumer<PartResultDto.Builder, List<PartDto>> partsSetter) { return search.stream() .map(s -> mapToPartResult(s, retrievalParam, partsSetter)) .filter(Objects::nonNull) .collect(Collectors.toList()); }
这样代码结构更松散,后续修改单一部分逻辑时更方便。
内容的提问来源于stack exchange,提问作者Thomas124124
相关产品推荐
相关产品推荐

