【设计模式】SpringBoot优雅使用策略模式

文章目录

  • 1.概述
    • 1.1.简述策略模式
    • 2.实现方法
      • 2.1.实现思路
      • 2.2.实现代码
      • 2.3.策略拓展
      • 2.4.执行调用
      • 3.总结

        1.概述

        本篇文章主要会描述SpringBoot与策略模式的结合使用,因为不涉及到理论部分,所以在阅读本篇之前,需要对策略模式的理论已经有了一个基本的了解。

        1.1.简述策略模式

        策略模式有3种角色,分别为:选择器、抽象策略、策略实例。

        其中选择器selector又被称为上下文context,其作用为通过不同的标识来获取对应的策略实例。策略实例就是封装不同算法的实例对象,而抽象策略就是策略实例的顶层接口。

        简单类图大概就是这个样子:

        2.实现方法

        我们在学习设计模式的时候会发现在各类模式中的类与对象都是手动创建的,而在日常的开发中,我们往往会将对象的生命周期交给Spring管理,也就是说,需要我们自行将各类bean组合成一个可运行的设计模式。

        假设我们有这样一个场景,需要对系统的中的数据做统计,需求中的统计维度分为:按周统计、按月统计,现使用策略模式来实现这个需求。

        2.1.实现思路

        • 前置设计:通过定义常量来标识策略的类型,使用者调用时可以通过常量获取对应的策略实例。
        • 策略设计:按周、按月分别对应两个bean实例,在内部各自实现对应的统计维度逻辑,在两个bean实例的上层是抽象策略,有一个通用的接口(或抽象类)用于对外提供访问入口。
        • 选择器设计:可以通过Map来存储数据,调用者调用时可以通过策略标识来获取对应的策略实例。

          可以看到,实现思路是比较简单的,现在的问题就是如何把策略的bean实例对象放到Map中。

          最简单的方式当然就是把对应的bean的Class对象直接写死在Map中,调用的时候可以通过applicationContext.getBean()获取到bean实例。但是这种方式不利于拓展,后续要新增一个策略实例的时候,还得修改这里的Map。

          第二种方式,我们可以通过解析注解来实现,给每个策略实例打上一个注解,注解中的值对应的就是按周、按月这样的常量标识,在SpringBoot启动时,通过扩展点扫描抽象策略,获取它所有的策略实例,解析注解后放入Map中。这种方式利于拓展,新增一个策略实例不需要对旧代码有任何改动。

          2.2.实现代码

          这里原本是使用自定义注解来实现的,经过评论区的提醒,对于注解实现这种方式,通过Spring中自带的@Component注解来实现会更加简洁,下面是去掉了自定义注解的实现方式。

          • 第一步:编写抽象策略与策略实例
            /**
             * 统计抽象策略处理器
             */
            public interface StatisticBaseHandler { void doStatistic();
            }
            
            import org.springframework.stereotype.Component;
            /**
             * 月统计策略
             */
            @Component("month")
            public class StatisticByMonthHandler implements StatisticBaseHandler { @Override
                public void doStatistic() { System.out.println("StatisticByMonthHandler");
                }
            }
            
            import org.springframework.stereotype.Component;
            /**
             * 周统计策略
             */
            @Component("week")
            public class StatisticByWeekHandler implements StatisticBaseHandler { @Override
                public void doStatistic() { System.out.println("StatisticByWeekHandler");
                }
            }
            
          • 第二步:编写选择器

            这里直接使用@Resouce或者@Autowired注入一个Map。

            import org.springframework.stereotype.Component;
            import javax.annotation.PostConstruct;
            import javax.annotation.Resource;
            import java.util.Map;
            /**
             * 统计策略选择器
             */
            @Component
            public class StatisticStrategySelector { @Resource
                private Map selectorMap;
                /**
                 * 根据类型选择对应的策略
                 *
                 * @param type 统计周期类型
                 * @return 统计抽象策略处理器
                 */
                public StatisticBaseHandler select(String type) { return selectorMap.get(type);
                }
            }
            

            启动完成之后,Map中的key值为@Component中配置的key。

            2.3.策略拓展

            现在需求发生了变化,需要加入一个按年统计的策略类型,只需要新增一个策略实例即可,如下:

            import org.springframework.stereotype.Component;
            /**
             * 年统计策略
             */
            @Component("year")
            public class StatisticByYearHandler implements StatisticBaseHandler { @Override
                public void doStatistic() { System.out.println("StatisticByYearHandler");
                }
            }
            

            其他的代码都不需要修改,再次查看Map中的值:

            2.4.执行调用

            随便写了一个测试方法,如下:

            public void invoke() { this.doInvoke("week");
                this.doInvoke("month");
                this.doInvoke("year");
            }
            public void doInvoke(String type) { StatisticBaseHandler handler = select(type);
                handler.doStatistic();
            }
            

            分别打印出了按周统计、按月统计、按年统计方法中的输出值,表示策略模式定义成功了,在我们实际的开放中,只需要前端(或其他调用端)传入对用的标识字符串,就可以执行不同的统计逻辑了。

            3.总结

            通过Spring获取接口的实现,并解析实现类上的注解的方式,可以在程序启动时动态的将策略注入到一个Map中,作为策略的容器。使用时传入标识符就可以获取到对应的策略执行了。