我在开发热部署的项目过程中,遇到了以下的情况:

有好几个热部署的场景,比如说:

  • 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表达式与函数式接口

抽象一般是对共性的部分进行提取。

那我们可以看到,上面提到的共性部分有:

  1. 每个处理器都有新增修改和回滚两个处理场景;
  2. 每个操作,不管成功失败都要记录日志;

首先,针对第一点:每个场景都有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 fileList, String transId) 方法传入一个表示,告诉我是新增修改还是回滚,然后再实现两个方法,去调对应的方法才行,那接口应该是:

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的魅力了吗