其他分享
首页 > 其他分享> > 可扩展代码设计(3):多扩展点门面

可扩展代码设计(3):多扩展点门面

作者:互联网

可扩展代码设计(1):if-else的痛
可扩展代码设计(2):消除if-else

上一篇文章 可扩展代码设计(2):消除if-else 提到,如果我们有很多的扩展逻辑,那么应该把相关的扩展逻辑归类到一起,形成多个扩展点接口。

本篇文章基于上篇文章的基础上,详细讲解如何组织多个扩展点:

一、归类出多个扩展点

首先,把扩展逻辑归类为多个扩展点。

比如,我们归类出3个扩展点接口:

1、抽象的扩展点接口:

package com.doing.plugin.extension;

/**
 * @author Doing on 19/12/2018 19:20
 */
public interface Extension {
}

2、3个归类后的扩展点接口:

package com.doing.plugin.extension;

/**
 * 扩展点
 *
 * @author Doing on 19/12/2018 15:38
 */
public interface PreAndPostExtension extends Extension{

    /**
     * 前置处理
     * @param params 参数
     * @param processRecord 处理对象传递
     */
    void beforeProcess(String params, StringBuilder processRecord);

    /**
     * 后置处理
     * @param params 参数
     * @param processRecord 处理对象传递
     */
    void afterProcess(String params, StringBuilder processRecord);
}

package com.doing.plugin.extension;

/**
 * @author Doing on 19/12/2018 19:21
 */
public interface ModifyExtension1 extends Extension {

    /**
     * @param params        参数
     * @param processRecord 处理对象传递
     */
    void modify(String params, StringBuilder processRecord);

}

package com.doing.plugin.extension;

/**
 * @author Doing on 19/12/2018 19:21
 */
public interface ModifyExtension2 extends Extension {

    /**
     * @param params        参数
     * @param processRecord 处理对象传递
     */
    void modify(String params, StringBuilder processRecord);

}

二、使用扩展点门面

现在我们有多个扩展点,每个新的活动业务接入进来,可以分别实现这几个扩展点接口。

但是,扩展逻辑分散到多个扩展点接口里后,业务系统共建者(接入新活动业务的开发同学)难免会困惑:现在系统都有哪些扩展点(扩展能力)?

所以,我们可以通过扩展门面去暴露出所有的扩展点:

package com.doing.plugin.extension;

/**
 * @author Doing on 19/12/2018 19:39
 */
public interface ExtensionFacade {

    PreAndPostExtension getPreAndPostExtension();

    ModifyExtension1 getModifyExtension1();

    ModifyExtension2 getModifyExtension2();
}

这样,参与活动业务共建的同学,便可以通过这个扩展门面知道都有哪些扩展点,然后根据自己的业务需求,挑选部分/全部扩展点进行实现。

比如,以京东活动为例子:

接入方法1、创建独立扩展类

创建独立的扩展类,实现各个扩展点接口:

package com.doing.plugin.extension.jd;

import com.doing.plugin.extension.PreAndPostExtension;

/**
 * @author Doing on 19/12/2018 15:38
 */
public class JdPreAndPostExtension implements PreAndPostExtension {

    @Override
    public void beforeProcess(String params, StringBuilder processRecord) {
        System.out.println("京东活动前置处理流程");
        processRecord.append("京东活动前置处理流程 - ");
    }

    @Override
    public void afterProcess(String params, StringBuilder processRecord) {
        System.out.println("京东活动后置处理流程");
        processRecord.append("京东活动后置处理流程");
    }
}

package com.doing.plugin.extension.jd;

import com.doing.plugin.extension.ModifyExtension1;

/**
 * @author Doing on 19/12/2018 19:26
 */
public class JdModifyExtension1 implements ModifyExtension1 {

    @Override
    public void modify(String params, StringBuilder processRecord) {
        System.out.println("京东活动优化处理流程1");
        processRecord.append("京东活动优化处理流程1 - ");
    }
}

package com.doing.plugin.extension.jd;

import com.doing.plugin.extension.ModifyExtension2;

/**
 * @author Doing on 19/12/2018 19:26
 */
public class JdModifyExtension2 implements ModifyExtension2 {

    @Override
    public void modify(String params, StringBuilder processRecord) {
        System.out.println("京东活动优化处理流程2");
        processRecord.append("京东活动优化处理流程2 - ");
    }
}

然后,实现扩展点门面接口:

package com.doing.plugin.extension.jd;

import com.doing.plugin.extension.ExtensionFacade;
import com.doing.plugin.extension.ModifyExtension1;
import com.doing.plugin.extension.ModifyExtension2;
import com.doing.plugin.extension.PreAndPostExtension;

/**
 * @author Doing on 19/12/2018 19:41
 */
public class JdExtensionFacade  implements ExtensionFacade {

    @Override
    public PreAndPostExtension getPreAndPostExtension() {
        return new JdPreAndPostExtension();
    }

    @Override
    public ModifyExtension1 getModifyExtension1() {
        return new JdModifyExtension1();
    }

    @Override
    public ModifyExtension2 getModifyExtension2() {
        return new JdModifyExtension2();
    }
}

接入方法2、匿名内部类实现

直接在扩展点门面中返回业务扩展的匿名内部类:

package com.doing.plugin.extension.jd;

import com.doing.plugin.extension.ExtensionFacade;
import com.doing.plugin.extension.ModifyExtension1;
import com.doing.plugin.extension.ModifyExtension2;
import com.doing.plugin.extension.PreAndPostExtension;

/**
 * @author Doing on 19/12/2018 19:41
 */
@BizCode("jd")
public class JdExtensionFacade  implements ExtensionFacade {

    @Override
    public PreAndPostExtension getPreAndPostExtension() {
        return new PreAndPostExtension() {
            @Override
            public void beforeProcess(String params, StringBuilder processRecord) {
                System.out.println("京东活动前置处理流程");
                processRecord.append("京东活动前置处理流程 - ");
            }

            @Override
            public void afterProcess(String params, StringBuilder processRecord) {
                System.out.println("京东活动后置处理流程");
                processRecord.append("京东活动后置处理流程");

            }
        };
//        return new JdPreAndPostExtension();
    }

    @Override
    public ModifyExtension1 getModifyExtension1() {
        return new ModifyExtension1() {
            @Override
            public void modify(String params, StringBuilder processRecord) {
                System.out.println("京东活动优化处理流程1");
                processRecord.append("京东活动优化处理流程1 - ");
            }
        };
//        return new JdModifyExtension1();
    }

    @Override
    public ModifyExtension2 getModifyExtension2() {
        return new ModifyExtension2() {
            @Override
            public void modify(String params, StringBuilder processRecord) {
                System.out.println("京东活动优化处理流程2");
                processRecord.append("京东活动优化处理流程2 - ");
            }
        };
//        return new JdModifyExtension2();
    }
}

如果业务逻辑不是非常复杂的话,匿名内部类实现会简洁点

三、使用注解标识扩展点

与前篇类似,我们还是要使用注解去标识出业务扩展点,才能被我们的扩展点管理框架自动加载。

注解:

package com.doing.plugin;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface BizCode {

    String value() default "taobao";

}

由于业务扩展门面列出了所有的扩展点,所以我们可以只给扩展门面加上注解即可。

比如,京东活动的扩展门面加上@BizCode("jd")

package com.doing.plugin.extension.jd;

import com.doing.plugin.BizCode;
import com.doing.plugin.extension.ExtensionFacade;
import com.doing.plugin.extension.ModifyExtension1;
import com.doing.plugin.extension.ModifyExtension2;
import com.doing.plugin.extension.PreAndPostExtension;

/**
 * @author Doing on 19/12/2018 19:41
 */
@BizCode("jd")
public class JdExtensionFacade  implements ExtensionFacade {

    @Override
    public PreAndPostExtension getPreAndPostExtension() {
        return new JdPreAndPostExtension();
    }

    @Override
    public ModifyExtension1 getModifyExtension1() {
        return new JdModifyExtension1();
    }

    @Override
    public ModifyExtension2 getModifyExtension2() {
        return new JdModifyExtension2();
    }
}

其他活动类似

四、自动识别和组装扩展点

基于注解和反射,我们可以自动识别和组装扩展点:

package com.doing.plugin;

import com.doing.plugin.extension.Extension;
import com.doing.plugin.extension.ExtensionFacade;
import org.reflections.Reflections;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Doing on 19/12/2018 16:20
 */
public class ExtensionBuilder {

    private ExtensionBuilder() {
    }

    public static ExtensionBuilder getInstance() {
        return ExtensionBuilderInstance.INSTANCE;
    }

    private static class ExtensionBuilderInstance {
        private static final ExtensionBuilder INSTANCE = new ExtensionBuilder();
    }

    /**
     * 扩展类 => (业务编码 => 业务扩展实例)
     */
    private Map<Class, ConcurrentHashMap<String, Extension>> multiExtMap = new ConcurrentHashMap<>();

    /**
     * 注册业务扩展类实例
     */
    public void build() {
        try {
            // 找出所有类型的扩展接口/类(继承或实现了Extension):这里主要是想找出继承于Extension的子接口,并不关心Extension子接口的实现类
            Reflections extKindReflections = new Reflections("com.doing.plugin.extension");
            Set<Class<? extends Extension>> extKindClasses = extKindReflections.getSubTypesOf(Extension.class);

            // 找出所有业务扩展门面类(继承或实现了ExtensionFacade)
            Reflections extFacadeReflections = new Reflections("com.doing.plugin.extension");
            Set<Class<? extends ExtensionFacade>> extFacadeClasses = extFacadeReflections.getSubTypesOf(ExtensionFacade.class);

            // 对每一种类型的扩展
            for (Class<? extends Extension> extKindClass : extKindClasses) {
                // 过滤Extension子接口的实现类
                if (!extKindClass.isInterface()) {
                    continue;
                }

                ConcurrentHashMap<String, Extension> extMap = new ConcurrentHashMap<String, Extension>();

                // 遍历所有扩展门面类,获取该类型扩展的业务实现
                for (Class<? extends ExtensionFacade> extFacade : extFacadeClasses) {
                    // 构建:业务编码 => 业务扩展实例
                    BizCode[] annotationsByType = extFacade.getAnnotationsByType(BizCode.class);
                    if (annotationsByType != null && annotationsByType.length > 0) {
                        BizCode bizCode = annotationsByType[0];
                        Extension extension = null;

                        Object extensionFacade = extFacade.newInstance();
                        Method[] facadeMethods = extFacade.getDeclaredMethods();
                        // 遍历扩展门面的所有方法
                        for (Method facadeMethod : facadeMethods) {
                            // 找到返回该类型扩展的method
                            if (extKindClass.isAssignableFrom(facadeMethod.getReturnType())) {
                                // 从业务扩展门面的方法返回中,获取该类型扩展的业务实现
                                extension = (Extension) facadeMethod.invoke(extensionFacade);
                            }
                        }
                        if (null != extension) {
                            extMap.put(bizCode.value(), extension);
                        }
                    }
                }

                multiExtMap.put(extKindClass, extMap);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据业务扩展类型、业务编码,获取对应的业务扩展类实例
     *
     * @param extClass 业务扩展类型
     * @param bizCode 业务编码
     * @return
     */
    public <Ext> Ext getExt(Class<Ext> extClass, String bizCode) {
        return (Ext) multiExtMap.get(extClass).get(bizCode);
    }
}

五、扩展点执行引擎

执行引擎会使用业务扩展点实现我们的业务逻辑流程:

package com.doing.engine;

import com.doing.plugin.ExtensionBuilder;
import com.doing.plugin.extension.ModifyExtension1;
import com.doing.plugin.extension.ModifyExtension2;
import com.doing.plugin.extension.PreAndPostExtension;

/**
 * @author Doing on 19/12/2018 13:20
 */
public class MultiExtensionEngine implements Engine {

    public MultiExtensionEngine() {
        // 注册扩展
        ExtensionBuilder.getInstance().build();
    }

    @Override
    public String process(String bizCode, String params) {
        StringBuilder processRecord = new StringBuilder();

        PreAndPostExtension preAndPostExtension = ExtensionBuilder.getInstance().getExt(PreAndPostExtension.class, bizCode);
        ModifyExtension1 modifyExtension1 = ExtensionBuilder.getInstance().getExt(ModifyExtension1.class, bizCode);
        ModifyExtension2 modifyExtension2 = ExtensionBuilder.getInstance().getExt(ModifyExtension2.class, bizCode);

        // 1、前置处理
        preAndPostExtension.beforeProcess(params, processRecord);
        // 2、统一处理1
        System.out.println("统一处理流程1");
        processRecord.append("统一处理1 - ");
        // 3、过程处理1
        modifyExtension1.modify(params,processRecord);
        // 4、统一处理2
        System.out.println("统一处理流程2");
        processRecord.append("统一处理2 - ");
        // 5、过程处理2
        modifyExtension2.modify(params,processRecord);
        // 6、统一处理3
        System.out.println("统一处理流程3");
        processRecord.append("统一处理3 - ");
        // 7、后置处理
        preAndPostExtension.afterProcess(params, processRecord);

        return processRecord.toString();
    }
}

包结构

包结构如下:

 

 

总结与分析

总结

本系统中,新的活动接入进来,只需要看下扩展点门面里都有哪些扩展点,然后按需实现自己的扩展点逻辑即可,其他工作都交给扩展点引擎框架去实现。

本设计可以提供一个比较清晰的开发框架,不至于由于扩展逻辑的繁杂而造成代码腐烂。

业务响应效率需求

通常来说,常规的开发方式是:每接入一个新的活动业务,我们都要走一遍如下流程:修改代码、编译构建运行调试(使用jrebel等热部署插件可以提效调试修改)、发布日常、发布预发、发布线上。

线上活动逻辑出问题了,也是要如此走一遍流程。

虽然我们的框架已经比较nice,但如果接入新的活动业务很频繁,或更新活动业务扩展逻辑很频繁,我们就要频繁地发布系统。

这样带来的不便在于:

这时,很多同学都会想到:能不能来个自动热部署呢?这样每个修改都可以及时生效。

但是,Java不是“脚本语言”,能实现自动热部署吗?

答案当然是肯定的,只是Java动态实现编译部署需要经过比较复杂的流程。

我们接下来可能需要分几篇文章来讲解下~

标签:extension,plugin,doing,代码,扩展,门面,com,public
来源: https://blog.csdn.net/zhousenshan/article/details/117845786