最新要闻

广告

手机

光庭信息跌4.57% 2021上市超募11亿2022扣非降74% 时快讯

光庭信息跌4.57% 2021上市超募11亿2022扣非降74% 时快讯

搜狐汽车全球快讯 | 大众汽车最新专利曝光:仪表支持拆卸 可用手机、平板替代-环球关注

搜狐汽车全球快讯 | 大众汽车最新专利曝光:仪表支持拆卸 可用手机、平板替代-环球关注

家电

每日消息!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表达式与函数式接口

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

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

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

首先,针对第一点:每个场景都有publish(), rollback()两个方法,我们可以考虑抽出一个接口,

public interface IHotSwapHandler {    /**     * 新增修改     */    boolean publish (List fileList, String transId);    /**     * 回退     */    boolean publish (List 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 fileList, String transId) {        return hotswap(fileList, transId);    }    @Override    public boolean rollback(List fileList, String transId) {        return hotswap(fileList, transId);    }        /**     * 遍历逻辑     **/    protected boolean hotswap(List 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(ListfileList, String transId) 方法传入一个表示,告诉我是新增修改还是回滚,然后再实现两个方法,去调对应的方法才行,那接口应该是:

public interface IHotSwapHandler {    /**     * 新增修改     *     * @param fileList 文件列表     * @param transId 流水     * @param hotswapType 类型     * @return     */    boolean publish (List fileList, String transId, HotswapTypeEnum hotswapType);    /**     * 回退     *      * @param fileList 文件列表     * @param transId 流水     * @param hotswapType 类型     * @return     */    boolean publish (List fileList, String transId, HotswapTypeEnum hotswapType);}

抽象实现类应该是:

public abstract class AbstractHotSwapHandler implements IHotSwapHandler {    @Override    public boolean commit(List fileList, String transId, HotswapTypeEnum.PUBLISH) {        return hotswap(fileList, transId);    }    @Override    public boolean rollback(List fileList, String transId, HotswapTypeEnum.ROLLBACK) {        return hotswap(fileList, transId);    }        /**     * 遍历逻辑     **/    protected boolean hotswap(List 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 fileList, String transId);    /**     * 回退     */    boolean publish (List fileList, String transId);}

新增一个FunctionInterface:LogFunction,用来处理日志记录

@FunctionalInterfacepublic 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 fileList, String transId, LogFunction logFunction) {        return hotswap(fileList, transId, logFunction);    }    @Override    public boolean rollback(List fileList, String transId, LogFunction logFunction) {        return hotswap(fileList, transId, logFunction);    }        /**     * 遍历逻辑     **/    protected boolean hotswap(List 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的魅力了吗

关键词: