引语
在 “代码可扩展的一个小技巧” 一文中谈到,要实现代码可扩展性,需要能够识别变化。
如何识别变化呢? 有两个症状:
-
相似模式的代码累积、膨胀;
-
switch 语句,每个分支有相似的代码。
通过这两点,就能判别出变化所在。现在变化频度越高,将来就更容易变化。
例子
下面是另一个例子。
新入侵检测里,需要存储大量 agent 上报的信息,比如进程树、文件、脚本、webfile、网络连接信息等。 代码如下:
// 进程树
List<ProcessTreeDTO> processTree = singleDetectionDetail.getProcessTree();
if (CollectionUtils.isNotEmpty(processTree)) {
elementDOList.addAll(buildProcessTrees(processTree));
}
// 文件信息
List<FileInfoDTO> file = singleDetectionDetail.getFile();
if (CollectionUtils.isNotEmpty(file)) {
elementDOList.addAll(buildFileInfos(agentId, file, fhashMap));
}
// 脚本文件
List<ScriptDTO> scripts = singleDetectionDetail.getScripts();
if (CollectionUtils.isNotEmpty(scripts)) {
elementDOList.addAll(buildScripts(agentId, scripts, fhashMap));
}
// web文件
List<WebfileDTO> webfile = singleDetectionDetail.getWebfile();
if (CollectionUtils.isNotEmpty(webfile)) {
elementDOList.addAll(buildWebFile(agentId, webfile, fhashMap));
}
显然,这样累积,这个类会膨胀得很厉害,也容易产生冲突。 如何重构呢?
通过代码分析,很容易就能知道,这些代码遵循相似的模式: 将一些上报的数据转换成元素对象列表。 据此,就可以定义接口:
/**
* 元素构建器
*/
public interface ElementBuilder {
/**
* 是否必要构建
* @param context 元素构建上下文语境
* @return 是否必要构建详情
*/
boolean need(ElementBuilderContext context);
/**
* 元素构建器
* @param context 元素构建上下文语境
* @return 构建的元素
*/
List<ElementDO> buildFrom(ElementBuilderContext context);
}
有童鞋可能会问: 明明参数只要 DTO 对象列表、agentId、fhashMap ,为什么要定义一个 ElementBuilderContext 呢?
其实,这是最终的结果。在重构的过程中,显然不是一步到位的。比如,我可能最开始只定义了 List
有了这个定义,原有的代码就很容易转换成一个组件类:
/**
* 文件元素构建
* Created by qinshu on 2022/3/9
*
* 源代码由许文进编写,由舒琴重构
*/
@Component
public class FileBuilder implements ElementBuilder {
/**
* 构建文件信息
*/
@Override
public List<ElementDO> buildFrom(ElementBuilderContext context) {
// codes for build elements
}
@Override
public boolean need(ElementBuilderContext context) {
SingleDetectionDetail agentDetectionDetail = context.getDetail();
return CollectionUtils.isNotEmpty(agentDetectionDetail.getFile());
}
最终,元素构建流程为:
ElementBuilderContext context = ElementBuilderContext.builder()
.agentId(agentId)
.xxx(xxx)
.build();
// 元素构建
for(ElementBuilder elementBuilder: elementBuilders) {
if (elementBuilder.need(context)) {
elementDOList.addAll(elementBuilder.buildFrom(context));
}
}
后续再需要构建新元素,只要添加实现 ElementBuilder 的子类即可。实现了开闭原则。