重构_使用模板模式替代重复业务操作

Posted fanyi0922

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了重构_使用模板模式替代重复业务操作相关的知识,希望对你有一定的参考价值。

 

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

 

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

 

  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库

以上是关于重构_使用模板模式替代重复业务操作的主要内容,如果未能解决你的问题,请参考以下文章

利用模板模式重构JDBC操作业务场景

模板模式

模板模式

Java面向对象(15)_模板方法设计模式

重构_使用设计模式替代多重判断

《Java8实战》 - 8.2.2 使用 Lambda 重构面向对象的设计模式 之 模板模式