fanyi0922

 

 

在日常开发中经常有重复的业务操作,每次写这些重复操作时总感觉冗余,又少点管理。今天我在开发中又遇到了这个问题,却发现项目组中的大牛已经帮我将重复的操作管理好了,于是便请教一番,偷偷学了这个---模板设计模式。

 

模板设计模式要注意以下几点:

 

  1. 将重复的业务操作抽取出一套模板;

     

  2. 将实现模板的实现类放到管理器中统一管理;

     

  3. 在业务中用的时候只用管理器,忽略模板的实现类。

 

掌握了要点之后,我们开始今天的实战阶段,项目中的需求是这样的:

 

  • 先找到有某一类型的货品的日志表,日志表有,货品与日志表的对应关系也有(货品与日志表一一对应)

     

  • 然后验证这个日志表中是否已经有核注信息(日志表的某一字段),返回结果;

     

  • 这样的日志表大概有 8-10 张。

 

需求其实特别简单,每张表查一遍就完事了,但问题是相同的逻辑,要写 10遍吗 ?如果多加一张表,还要改动主流程的逻辑吗?有没有更好的方法呢?

 

下面我们将重复的工作模板化,首先拆出一个模板类:

 

/**
 * 抽象模板,定义所有 processor 的处理逻辑
 *
 * @author fanpengyi
 * @version 1.0
 * @date 2019-7-28
 */
public abstract class AbstractTemplateProcessor<T> implements DataSupplier<T>, InitializingBean {

    //所有的商品类型
    private Set<CertType> types;

    public AbstractTemplateProcessor() {
        this.types = getTypes();
    }

    /**
     * 抽象模板的执行方法
     *
     * @param paramList 业务层传过来的需要的​参数
     * @param types     业务层传过来d额商品类型
     */
    public Response<Void> process(ParamList paramList, Set<CertType> types) {
        //获取参数
        T supply = supply(paramList);
        //process的判断
        if (accept(supply, types)) {
            //真正执行 process
            return doProcess(paramList.getUseId());
        }
        return Response.success();
    }

    /**
     * 判断 process 执行方法的前置判断
     *
     * @param projectCode 商品编号
     * @param types       商品类型​
     * @return
     */
    protected abstract boolean accept(T projectCode​, Set<CertType> types);

    /**
     * 真正执行 process 方法
     *
     * @param bizId​
     */
    protected abstract Response<Void> doProcess(String biz​Id);

    /**
     * 获取每个 processor 支持的类型
     *
     * @return
     */
    public abstract Set<CertType> getTypes();

    /**
     * 加载类的后置处理,将processor注册到​处理器中
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        TemplateProcessorMananger.getInstance().register(this);

    }

}
/**
 * 用于向抽象模板中传递需要的参数
 * @author fanpengyi
 * @version 1.0
 * @date 2019-7-28
 */
public interface DataSupplier<T> {

    /**
     * 根据 paramList 获取每个 processor 需要处理对象
     * @param paramList 参数列表
     * @return
     */
    T supply(ParamList paramList);
}

由模板类最后可知,需要一个 processor 的管理器,管理器需要注意几点:

 

  • 管理器必须是单例的

     

  • 管理器的操作都需要加锁,保证线程安全

 

/**
 * process 管理器 用于注册 process 与 执行所有的 process
 * @author fanpengyi
 * @version 1.0
 * @date 2019-7-28
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class TemplateProcessorMananger {

    /**
     *静态的引用,单例
     */
    @Getter
    private static TemplateProcessorMananger Instance = new TemplateProcessorMananger();

    //注册管理器,统一放置 process,如果有顺序 需要注意顺序
    private List<AbstractTemplateProcessor> processors = new LinkedList<>();

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    /**
     * 注册方法
     */
    public void register(AbstractTemplateProcessor processor){

        try{
            //加锁是保证安全,不然可能报错 CopyOnWriteList 是线程安全的
            lock.writeLock().lock();
            processors.add(processor);
        }finally{
            lock.writeLock().unlock();
        }
    }



    public List<Response<Void>> process(ParamList paramList, Set<CertType> types){

        try{
            //加锁是为了在读的时候不允许其他线程操作 processors
            lock.readLock().lock();
            return processors.stream()
                    .map( o-> (Response<Void>)o.process(paramList,types))
                    .collect(Collectors.toList());
        }finally{
            lock.readLock().unlock();
        }



    }


}

 

 

除了以上的模板类和管理器外,还需要定义一个全局的参数,保存每个具体的 processor 的结果,由于是并发访问,不能保证每次访问的商品类型一致,所以此处我们采用 ThreadLocal 的形式,逐级传递,保证每个线程参数的安全性,而全局参数的初始化与销毁到放在了拦截器中实现。

 

/**
 * 每个线程私有的变量,类似于全局变量,但是每个线程自己有自己的一份
 * 放在 拦截器中初始化 和 销毁,有初始化,有销毁,不然会有内存泄漏的风险
 *
 * @author fanpengyi
 * @version 1.0
 * @date 2019-7-28
 */
public class DataHolder {

    private static ThreadLocal<ParamList> threadLocal;

    /**
     * 初始化 theadLocal
     */
    public static void start(){

        if(threadLocal == null){
            threadLocal = new ThreadLocal<>();
        }

        if(threadLocal.get() == null){
            threadLocal.set(new ParamList());
        }

    }

    public static ParamList get(){return threadLocal.get();}

    public static void update(ParamList paramList){threadLocal.set(paramList);}

    public static void shutdown(){
        if(threadLocal != null){
            threadLocal.remove();
        }
    }

}
/**
 * 拦截器中初始化 和 销毁 threadLocal
 * @ConditionalOnClass(Controller.class) -> 执行条件 有Controller 类时执行
 * @author fanpengyi
 * @version 1.0
 * @date 2019-7-28
 */
@Configuration
@ConditionalOnClass(Controller.class)
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(new HandlerInterceptor() {

            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                DataHolder.start();
                return true;
            }

            @Override
            public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
                //ingore 不做处理
            }

            @Override
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
                DataHolder.shutdown();
            }
        });

    }
}

 

 

有了以上三个核心类,我们只需要创建抽象模板的实现类,每个 processor 实现自己的方法,互不影响,最终都会被注册进管理器中,在业务方法中调用管理器的 process 方法就可以达到根据商品类型不同调用不同的processor 的目的了。

 

    /**
     * 业务方法
     */
    public void doTemplate(){

        // 准备参数
        ParamList paramList = new ParamList();
        paramList.setUseId("6666");

        Set<CertType> sets = new HashSet<>();
        sets.add(CertType.TYPE_3);
        sets.add(CertType.TYPE_1);
        TemplateProcessorMananger.getInstance().process(paramList,sets);

        //预期 只会执行 1 ,3的processor
        Map<String, Object> resultMaps = DataHolder.get().getResultMaps();
        for (String s : resultMaps.keySet()) {
            log.info(s+"------>"+resultMaps.get(s));
        }

    }
}

 

 

使用模板模式替代重复的业务操作,优点在于将重复操作统一管理,添加新的业务时对主流程侵入小,易于扩展,易于管理。

 

参考资料:

https://github.com/fanpengyi/template 

                  ---- 文中代码git库

分类:

Java

技术点:

相关文章: