java基础之行为抽象
我在开发热部署的项目过程中,遇到了以下的情况:
有好几个热部署的场景,比如说:
- java类热部署(场景A)
- mybatis的xml热部署(场景B)
- 属性配置文件热部署(场景C)
然后这些场景大致有两种热部署的操作,那就是:
- 操作a:新增与修改;
- 操作b:回退,
而所有的场景的所有操作,处理的文件是一个列表,处理的场景需要根据文件后缀自己区分,每个文件的处理不管成功失败都要记录日志。
这是基本的场景。刚开始的写法极为不内聚:
1、写两个操作处理类,用来处理新增修改和回滚操作
2、每个场景分别写一个处理器,每个处理器写两个主要方法分别处理两个操作:publish(), rollback();
3、每个操作处理类先将两个文件列表根据进行分类,分别区分出三个场景的文件列表,分别调用对应处理器的操作方法
4、由于每个操作都需要记录日志,分失败成功两个情况,那就要在3中提到6个操作方法中对这两种情况进行日志记录。
我们来看一下上面写法的特点:
步骤1 涉及2个类,每个类3个判断
步骤2涉及3个场景类6个场景方法
步骤3涉及2个操作类6个操作方法
步骤4涉及12个类似的处理
以上的方案不能说错误,但是是一种充满不合理的正确。
我们来分析一下,能够如何更好的进行抽象,让我们的代码看起来更加的优雅。
我们经常对我们开发过程中碰到过的实体类进行抽象,详细大家并不陌生。比如对一下公共的属性进行抽象,但自从jdk8发布以来,方法也称为了“一等公民”, 跟我们的数据类型都拥有相同的地位了,我们要适应对行为动作进行抽象。
对函数式接口还不熟悉的小伙伴,可以移步另外一篇文章先做了解:lamda表达式与函数式接口
抽象一般是对共性的部分进行提取。
那我们可以看到,上面提到的共性部分有:
- 每个处理器都有新增修改和回滚两个处理场景;
- 每个操作,不管成功失败都要记录日志;
首先,针对第一点:每个场景都有publish(), rollback()两个方法,我们可以考虑抽出一个接口,
public interface IHotSwapHandler {
/**
* 新增修改
*/
boolean publish (List<FileInfo> fileList, String transId);
/**
* 回退
*/
boolean publish (List<FileInfo> fileList, String transId);
}
然后每个场景作不同的实现。
那针对第二点,我们要尝试将这12个try-catch然后记录日志的动作整合成一个,因为这12个处理的不同,其实只是try里面的部分。我们可以把这12个处理简化成下面的步骤:
for (FileInfo fileinfo : fileList) {
try {
// 新增修改/回滚 操作
}
catch (Throwable e) {
// 记录日志
}
}
可以看到,距离最后的“殊途同归“,还差新增修改/回滚 操作的抽象,而遍历的动作,是每一个场景每个处理都要做。针对这种所有实现类的所有方法都要做步骤,我们考虑在抽象实现类中实现,相当于众多实现类大家长,把各自场景都需要做的给处理了,让没个场景实现类的实现更加纯粹
那我们先实现一个抽象类
public abstract class HotSwapHandler implements IHotSwapHandler {
@Override
public boolean commit(List<FileInfo> fileList, String transId) {
return hotswap(fileList, transId);
}
@Override
public boolean rollback(List<FileInfo> fileList, String transId) {
return hotswap(fileList, transId);
}
/**
* 遍历逻辑
**/
protected boolean hotswap(List<FileInfo> fileList, String transId) {
boolean success = true;
if (CollectionUtils.isEmpty(fileList) || Objects.isNull(logFunction)) {
return success;
}
for (FileInfo fileInfo : fileList) {
boolean result = 【新增修改\回退操作】
if (!result) {
success = result;
// 失败后中断发布
break;
}
}
return success;
}
}
如此一来,抽象类实现的新增修改\回退操作都已经走了相同的遍历逻辑了,最后各自的具体实现逻辑了。抽象实现类只做了他力所能及的事情,帮你们把遍历逻辑统一了起来,就是上面简单模型中的
for (FileInfo fileinfo : fileList) {
}
但新增修改\回退操作需要正式各自实现类去实现,
try {
// 新增修改/回滚 操作
}
catch (Throwable e) {
// 记录日志
}
但细心的同学会发现,新增修改/回滚 操作是两个不同的操作呀,按正常的写法我要在 protected boolean hotswap(List
public interface IHotSwapHandler {
/**
* 新增修改
*
* @param fileList 文件列表
* @param transId 流水
* @param hotswapType 类型
* @return
*/
boolean publish (List<FileInfo> fileList, String transId, HotswapTypeEnum hotswapType);
/**
* 回退
*
* @param fileList 文件列表
* @param transId 流水
* @param hotswapType 类型
* @return
*/
boolean publish (List<FileInfo> fileList, String transId, HotswapTypeEnum hotswapType);
}
抽象实现类应该是:
public abstract class AbstractHotSwapHandler implements IHotSwapHandler {
@Override
public boolean commit(List<FileInfo> fileList, String transId, HotswapTypeEnum.PUBLISH) {
return hotswap(fileList, transId);
}
@Override
public boolean rollback(List<FileInfo> fileList, String transId, HotswapTypeEnum.ROLLBACK) {
return hotswap(fileList, transId);
}
/**
* 遍历逻辑
**/
protected boolean hotswap(List<FileInfo> fileList, String transId, HotswapTypeEnum hotswapType) {
boolean success = true;
if (CollectionUtils.isEmpty(fileList) || Objects.isNull(logFunction)) {
return success;
}
for (FileInfo fileInfo : fileList) {
if(hotswapType == HotswapTypeEnum.PUBLISH) {
realPublish(fileInfo, transId);
}
if(hotswapType == HotswapTypeEnum.ROLLBACK) {
realRollback(fileInfo, transId);
}
boolean result = 【新增修改\回退操作】
if (!result) {
success = result;
// 失败后中断发布
break;
}
}
return success;
}
protected abstract boolean realPublish(FileInfo fileInfo, String transId);
protected abstract boolean realRollback(FileInfo fileInfo, String transId);
}
然后每个实现类继承AbstractHotSwapHandler,然后本质是实现抽象实现类的两个方法
protected abstract boolean realPublish(FileInfo fileInfo);
protected abstract boolean realRollback(FileInfo fileInfo);
这样似乎很完美了,但你会发现,其实我们要解决的第二点还没有解决,因为当你去写实现类的时候,还是会发现,你每个实现类的realPublish和realRollback都需要写一下的逻辑
try {
// 操作
}
catch (Throwable e) {
// 记录日志
}
操作操作,当我们把两种类型的操作都看成是一个行为的时候,我们就知道改如何去抽象出这一层了,此时我们的新晋“一等公民”就得登场了:FunctionInterface,它允许你将一个动作作为参数传入来。然后直接调用他的处理方法就行了。所以,我们需要一个能把新增修改/回滚看成同一个类型的类,传入尽量,我们就可以将上面的try-catch做公共处理了。所以既然是同一个类型,我们就不用标识来判断了,接口可以改回去
public interface IHotSwapHandler {
/**
* 新增修改
*/
boolean publish (List<FileInfo> fileList, String transId);
/**
* 回退
*/
boolean publish (List<FileInfo> fileList, String transId);
}
新增一个FunctionInterface:LogFunction,用来处理日志记录
@FunctionalInterface
public interface LogFunction {
default boolean apply(FileInfo fileInfo, String transId) {
boolean success = true;
if (Objects.isNull(fileInfo)) {
return success;
}
try {
doApply(fileInfo);
HotSwapHelper.saveHotswapSuccLog(transId, fileInfo.getFileId());
}
catch (Throwable e) {
// 日志记录
HotSwapHelper.saveHotswapErrorLog(transId, fileInfo.getFileId());
success = false;
}
return success;
}
/**
* 处理单个文件,兼容发布与回滚
* @param fileInfo 单个文件信息
* @throws Throwable
*/
void doApply(CommandItemFile commandItemFile) throws Throwable;
}
相当于实现类其实只需要去实现void doApply(CommandItemFile commandItemFile) throws Throwable;这个方法,而由于FunctionInterface的特殊性,他能够被看成一个特殊的数据类型,而不用再新增类去实现这个接口。
依次而抽象实现类去除标识判断,
public abstract class AbstractHotSwapHandler implements IHotSwapHandler {
@Override
public boolean commit(List<FileInfo> fileList, String transId, LogFunction logFunction) {
return hotswap(fileList, transId, logFunction);
}
@Override
public boolean rollback(List<FileInfo> fileList, String transId, LogFunction logFunction) {
return hotswap(fileList, transId, logFunction);
}
/**
* 遍历逻辑
**/
protected boolean hotswap(List<FileInfo> fileList, String transId, LogFunction logFunction) {
boolean success = true;
if (CollectionUtils.isEmpty(fileList) || Objects.isNull(logFunction)) {
return success;
}
for (FileInfo fileInfo : fileList) {
boolean result = logFunction.apply(fileInfo);
if (!result) {
success = result;
// 失败后中断发布
break;
}
}
return success;
}
protected abstract LogFunction realPublish();
protected abstract LogFunction realRollback();
}
因此你会看到,新增修改和回退两个操作,其实都是被看成一个行为,只是这个行为的内容不一样样,就想我们一个普通的Inger类型,它们都是整数,但可能数值是不一样的。
FunctionInterface同样的道理,所有的FunctionInterface都可以说是对行为的抽象,也就是对方法的抽象,然后可以对这些方法做共性的处理,因此对FunctionInterface的定义其实很简单,只要是要对不同的行为做相同的处理,都可以定义为FunctionInterface。你也可以简单的理解,FunctionInterface的作用就是,请你告诉我一段处理逻辑,我要拿着这段处理逻辑去用,其他事情我帮你做了,你不用操心。是不是很玄幻,又那么招人喜欢。
你感受到FunctionInterface的魅力了吗