zoukankan      html  css  js  c++  java
  • 使用模板方法模式动态绑定多实现类实例

    摘要

      摘要: 由于业务场景复杂,一个算法需要开发行为变化多端的多个实现类,然后在系统运行时根据不同场景装载不同的类实例。为了使源码具有更好的可扩展性和可重用性,在借鉴前人处理方法的基础上,介绍在Spring项目中,基于模板方法模式介绍一个接口被多个实现类实现时,Spring框架怎样从容器中正确取出我们想要的实例。

    前言

      除了《Spring注解之@Autowired:按类型自动装配Bean到数组、集合和Map》介绍的几种注入bean方法之外,还有其它方法,本文绍如何使用模板方法模式装配 bean。

      在阎宏博士的《JAVA与模式》一书中开头是这样描述模板方法模式(Template Method Pattern)的:

      模板方法模式是类的行为模式。准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法(类的多态实现),从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。

      欲了解更多相关知识点请移步《Spring 动态绑定多实现类实例综述》。

    业务场景回顾

      需求描述:定制一个绘图工具,她根据客户端发送的指令可以画出正方形、矩形、圆形和三角形等各种各样的几何图形。例如,当客户端需要绘制三角形的时候,就调用绘制三角形的方法;当需要绘制圆形的时候,就调用绘制圆形的方法。

    模板方法模式中的方法

      模板方法中的方法可以分为两大类:模板方法和基本方法。
     

    模板方法

      一个模板方法是定义在抽象类中的,把基本操作方法组合在一起形成一个总算法或一个总行为的方法。

      一个抽象类可以有任意多个模板方法,而不限于一个。每一个模板方法都可以调用任意多个具体方法。

    基本方法

     基本方法又可以分为三种:抽象方法(Abstract Method)、具体方法(Concrete Method)和钩子方法(Hook Method)。
      ● 抽象方法:一个抽象方法由抽象类声明,由具体子类实现。在Java语言里抽象方法由关键字abstract修饰。
      ● 具体方法:一个具体方法由抽象类声明并实现,而子类并不实现或覆盖。
      ● 钩子方法:一个钩子方法由抽象类声明,而子类负责实现。

    public abstract class ShapeTem {
        //模板方法
        public final void getTime() {
            long start = System.currentTimeMillis();
            draw();
            System.out.println("耗时 " + (System.currentTimeMillis()  - start) + " 毫秒");
        }
        /**
         * 画图实现方法
         */
        public abstract void draw();
        /**
         * 基本方法(空方法)<br/>
         * 检验实现类是否支持 beanName 指定的画图方式
         * @param beanName 画图工具具体实现类的 bean 名称
         * @return
         */
        public abstract Boolean hook(String beanName);
    }
    

      抽象类ShapeTem中的模板方法实际上是提供了一个外部可访问接口,使得外部环境由该接口获得服务;之所以为模板方法添加修饰符 final,是因为这样它就不会被重写,避免被恶意操作。在每个具体模板角色类中实现抽象类所声明的两个基本方法,下面以画三角形和长方形为例进行说明,圆形和正方形等的实现类请自行添加:

    @Component
    public class TriangleTem extends ShapeTem {
        /**
         * 画图实现方法
         */
        @Override
        public void draw() {
            System.out.println("Inside Triangle::draw() method.");
        }
    
        /**
         * 基本方法<br/>
         * 检验实现类是否支持 beanName 指定的画图方式
         *
         * @param beanName 画图工具具体实现类的 bean 名称
         * @return
         */
        @Override
        public Boolean hook(String beanName) {
            return "triangleTem".equals(beanName);
        }
    }
    
    @Component
    public class RectangleTem extends ShapeTem {
        /**
         * 画图实现方法
         */
        @Override
        public void draw() {
            System.out.println("Inside Rectangle::draw() method.");
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 基本方法<br/>
         * 检验实现类是否支持 beanName 指定的画图方式
         *
         * @param beanName 画图工具具体实现类的 bean 名称
         * @return
         */
        @Override
        public Boolean hook(String beanName) {
            return "rectangleTem".equals(beanName);
        }
    }
    

      客户端可根据需要使用不同的模板实现。新增 ShapeTemplate 类,通过 applicationContext 获取接口不同实现类的bean,并由方法 toDraw(String beanName)根据beanName确认画图模板,最终找到画图的具体方法。

    import com.eg.wiener.service.ShapeTem;
    import org.apache.commons.collections4.CollectionUtils;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.stereotype.Component;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    
    /**
     * 模板方法模式
     *
     * @author Wiener
     * @date 2021/1/18
     */
    @Component
    public class ShapeTemplate implements ApplicationContextAware, InitializingBean {
    
        private ApplicationContext applicationContext;
        /**
         * 1610543593
         * 通过 applicationContext 获取抽象类的bean,和通过@Autowired注解的方式获取接口实现类的bean有着异曲同工之妙
         */
        private List<ShapeTem> shapeList = null;
    
        @Autowired
        private Map<String, ShapeTem> shapeTemMap;
        
        @Override
        public void afterPropertiesSet() throws Exception {
            if (shapeList == null) {
                shapeList = new ArrayList<>();
                Map<String, ShapeTem> beansOfType = applicationContext.getBeansOfType(ShapeTem.class);
                beansOfType.forEach((key, value) -> shapeList.add(value));
            }
            if (CollectionUtils.isEmpty(shapeList)) {
                System.out.println("--------- 获取抽象类的bean失败 --------------");
            }
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    
        public void toDraw(String beanName) {
            System.out.println("通过 @Autowired 注解的方式获取实现类:" + shapeTemMap);
            System.out.println("通过 ApplicationContext 的方式获取实现类:" + shapeList);
            for (ShapeTem oneShape : shapeList) {
                if (oneShape.hook(beanName)) {
                    oneShape.getTime();
                    return;
                }
            }
            System.out.println("beanName非法!");
        }
    }
    

      有了上面的代码之后,我们在控制层创建测试用例来看一下效果,测试代码片段如下:

        /**
         * 由模板方法模式实现
         * @param code
         * @return
         */
        @GetMapping("/drawByTemplate")
        public String drawByTemplate(String code) {
    
            shapeTemplate.toDraw(code);
            return "由模板方法模式实现成功";
        }
    

        执行结果:

    通过 @Autowired 注解的方式获取实现类:{rectangleTem=com.eg.wiener.service.impl.RectangleTem@1f6ce7aa, triangleTem=com.eg.wiener.service.impl.TriangleTem@11ab1a37}
    通过 ApplicationContext 的方式获取实现类:[com.eg.wiener.service.impl.RectangleTem@1f6ce7aa, com.eg.wiener.service.impl.TriangleTem@11ab1a37]
    Inside Rectangle::draw() method.
    耗时 300 毫秒

    服务端根据不同的请求参数code,调用不同的示例。

        执行结果分析

    1. 通过两种方式所得到的实现类的bean一模一样,说明二者有着异曲同工之妙。但是,通过注解的方法获取时,实现逻辑更精简。
    2. 通过 applicationContext 获取实现类的bean后,我们把这些bean放入集合shapeList。在客户端画图的时候,如果传入的参数beanName跟自己定义的一样,则调用当前画图类实例的draw方法。

        当增加一个新的实现类时,不要按照控制流程的思路去思考问题,而应当按照“责任”的思路去规划。换言之,应当考虑哪些操作是新的实现类必须置换掉的,哪些操作是可以置换掉的,以及哪些操作是不可以置换掉的。使用模板方法设计模式可以使这些责任变得清晰。

    结束语

        这篇文章主要向大家介绍如何使用模板方法模式实现多个接口,就算每个实现类的逻辑变化多端,只要执行流程是固定的,使用该模式都能让我们的编码效率、维护效率和扩展效率更上一层楼。

    应用实例 做试卷时,考生题目都是一样的,只是答案千差万别。
      

    Reference


      读后有收获,小礼物走一走,请作者喝咖啡。

    赞赏支持

  • 相关阅读:
    REDUCING THE SEARCH SPACE FOR HYPERPARAMETER OPTIMIZATION USING GROUP SPARSITY阅读笔记
    缓存穿透、缓存击穿、缓存雪崩区别和解决方案
    Jenkins中构建时提示:Couldn't find any revision to build. Verify the repository and branch config
    Docker中使用Dockerfile定制化jar启动时:at sun.awt.FontConfiguration.getVersion(FontConfiguration.java:1264)
    Docker中部署mysql后SpringBoot连接时提示表不存在(修改表名忽略大小写)
    js使用y-seal实现印章功能
    手写js原生方法总结(简版)
    P5666
    CF653G
    P4649
  • 原文地址:https://www.cnblogs.com/east7/p/14390796.html
Copyright © 2011-2022 走看看