zoukankan      html  css  js  c++  java
  • Spring 原生注解来快速实现 策略模式 + 工厂模式

    前言

    这阵子在做项目组重构的工作,工作中的一部分就是就目前代码库中与企业交互的逻辑抽离出来,单独做一个微服务,实现企业交互逻辑的关注点分离。

    在这里面我很自然而然的就用到了策略模式 + 工厂模式的方式,包装内部实现细节,向外提供统一的调用方式,有效的减少if/else的业务代码,使得代码更容易维护,扩展。

    之前看过一些文章,是使用自定义注解+自动BeanProcessor的方式来实现,个人感觉有点麻烦。因为Spring原生就提供类似的特性。

    本篇旨在介绍在实际的业务场景,如何借助Spring IoC 依赖注入的特性,使用Spring 原生注解 来快速实现 策略模式 + 工厂模式。希望能够对你有启发。

    业务场景

    从原项目抽离出来的企业服务,承担的是与外部企业交互的职责。不同企业,虽然会产生的交互行为是相同的,但是交互行为内部的实现逻辑各有不同,比如发送报文接口,不同企业可能报文格式会不同。

    针对这种不同企业交互细节不同的场景,将与企业的交互行为抽象出来EntStrategy接口,根据服务消费者传入的企业号选择对应的实现类(策略类),逻辑简化之后如下图。

    快速实现

    现在让我们用快速用一个DEMO实现上述场景。

    我们的期望目标是,根据不同的企业编号,我们能够快速找到对应的策略实现类去执行发送报文的操作。

    Step 1 实现策略类

    假设我们现在对外提供的服务Api是这样的,

    /**
     * @param entNum 企业编号
     */
    public void send(String entNum) {
        // 根据不同的企业编号,我们能够快速找到对应的策略实现类去执行发送报文的操作
    }

    现在我们先定义个EntStrategy接口

    /**
     * @author Richard_yyf
     * @version 1.0 2019/10/23
     */
    public interface EntStrategy {
    
        String getStuff();
    
        void send();
    }

    三个策略类

    DefaultStrategy

    @Component
    public class DefaultStrategy  implements EntStrategy {
        @Override
        public String getStuff() {
            return "其他企业";
        }
    
        @Override
        public void send() {
            System.out.println("发送默认标准的报文给对应企业");
        }
    
        @Override
        public String toString() {
            return getStuff();
        }
    }

    EntAStrategy

    @Component
    public class EntAStrategy implements EntStrategy {
        @Override
        public String getStuff() {
            return "企业A";
        }
    
        @Override
        public void send() {
            System.out.println("发送A标准的报文给对应企业");
        }
    
        @Override
        public String toString() {
            return getStuff();
        }
    }

    EntBStrategy

    @Component
    public class EntBStrategy implements EntStrategy {
        @Override
        public String getStuff() {
            return "企业B";
        }
    
        @Override
        public void send() {
            System.out.println("发送B标准的报文给对应企业");
        }
    
        @Override
        public String toString() {
            return getStuff();
        }
    }

    Step 2 借助Spring 强大的依赖注入

    下面的设计是消除if/else的关键代码,这里我定义了一个EntStrategyHolder来当做工厂类。

    @Component
    public class EntStrategyHolder {
    
        // 关键功能 Spring 会自动将 EntStrategy 接口的类注入到这个Map中
        @Autowired
        private Map<String, EntStrategy> entStrategyMap;
    
        public EntStrategy getBy(String entNum) {
            return entStrategyMap.get(entNum);
        }
    }

    这一步的关键就是, Spring 会自动将 EntStrategy 接口的实现类注入到这个Map中。前提是你这个实现类得是交给Spring 容器管理的。

    这个Map的key值就是你的 bean id,你可以用@Component("value")的方式设置,像我上面直接用默认的方式的话,就是首字母小写。value值则为对应的策略实现类。

     到这一步,实际上我们的期望功能大部分已经实现了,先让用一个简单的启动类试一下。

    /**
     * @author Richard_yyf
     * @version 1.0 2019/10/23
     */
    @Configuration
    @ComponentScan("ric.study.demo.ioc.autowire_all_implementation_demo_set")
    public class Boostrap {
    
        public static void main(String[] args) {
            String entNum = "entBStrategy";
            send(entNum);
            entNum = "defaultStrategy";
            send(entNum);
        }
    
        // 用这个方法模拟 企业代理服务 提供的Api
        public static void send(String entNum) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Boostrap.class);
            context.getBean(EntStrategyHolder.class).getBy(entNum).send();
        }
    }

    输出结果

    发送B标准的报文给对应企业
    发送默认标准的报文给对应企业

    Step 3 别名转换

    大家眼睛如果稍微利索的点的话,会发现我上面启动类里面的企业编号entNum填的实际上是bean id的值。那在实际业务中肯定是不会这样的,怎么可能把一个企业编号定义的这么奇怪呢。

    所以这里还需要一步操作,将传入的企业编号,转义成对应的策略类的bean id。

    实际上这一步的逻辑和你的实际业务是有很强的相关性的,因为在我业务里面的entNum在实际上就是一种标识,程序怎么识别解析这个标识,找到对应的策略实现类,应该是根据你的业务需求定制的。

    我这里把这一步也写出来,主要是想给大家提供一种思路。

    因为我的微服务是用SpringBoot做基础框架的,所以我借助SpringBoot 外部化配置的一些特性实现了这种方式。

    添加EntAlias类

    /**
     * @author Richard_yyf
     * @version 1.0 2019/10/15
     */
    @Component
    @EnableConfigurationProperties
    @ConfigurationProperties(prefix = "ent")
    public class EntAlias {
    
        private HashMap<String, String> aliasMap;
    
        public static final String DEFAULT_STATEGY_NAME = "defaultStrategy";
    
        public HashMap<String, String> getAliasMap() {
            return aliasMap;
        }
    
        public void setAliasMap(HashMap<String, String > aliasMap) {
            this.aliasMap = aliasMap;
        }
    
        String of(String entNum) {
            return aliasMap.get(entNum);
        }
    }

    在对应配置文件application.yml中配置:

    ent:
      aliasMap:
        entA: entAStrategy
        entB: entBStrategy
    
    ....省略

    这里注意哦,要实现对应getter和setter的,不然属性会注入不进去的。

    改写一下EntStrategyHolder类

    @Component
    public class EntStrategyHolder {
    
        @Autowired
        private EntAlias entAlias;
    
        // 关键功能 Spring 会自动将 EntStrategy 接口的类注入到这个Map中
        @Autowired
        private Map<String, EntStrategy> entStrategyMap;
    
        // 找不到对应的策略类,使用默认的
        public EntStrategy getBy(String entNum) {
            String name = entAlias.of(entNum);
            if (name == null) {
                return entStrategyMap.get(EntAlias.DEFAULT_STATEGY_NAME);
            }
            EntStrategy entStrategy = entStrategyMap.get(name);
            if (entStrategy == null) {
                return entStrategyMap.get(EntAlias.DEFAULT_STATEGY_NAME);
            }
            return entStrategy;
        }
    }

    现在我们再启动一下看看:

    /**
     * @author Richard_yyf
     * @version 1.0 2019/10/23
     */
    @Configuration
    @ComponentScan("ric.study.demo.ioc.autowire_all_implementation_demo_set")
    public class Boostrap {
        public static void main(String[] args) {
            String entNum = "entA";
            send(entNum);
            entNum = "entB";
            send(entNum);
            entNum = "entC";
            send(entNum);
        }
    
        public static void send(String entNum) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Boostrap.class);
            context.getBean(EntStrategyHolder.class).getBy(entNum).send();
        }
    }

    输出结果

    发送A标准的报文给对应企业
    发送B标准的报文给对应企业
    发送默认标准的报文给对应企业

    非SpringBoot

    上面的代码中我采用SpringBoot的特性,通过yml文件来管理别名转化,是为了让代码看起来更美观。如果是Spring框架的话。我会这样去实现。

    /**
     * @author Richard_yyf
     * @version 1.0 2019/10/23
     */
    public class EntAlias {
    
        private static Map<String, String> aliasMap;
    
        private static final String ENTA_STATEGY_NAME = "entAStrategy";
        private static final String ENTB_STATEGY_NAME = "entBStrategy";
        public static final String DEFAULT_STATEGY_NAME = "defaultStrategy";
    
        static {
            // 这个别名容器怎么注册别名、初始化,有很多种方式。
            aliasMap = new LinkedHashMap<>();
            aliasMap.put("entA", ENTA_STATEGY_NAME);
            aliasMap.put("entB", ENTB_STATEGY_NAME);
        }
    
        public static String of(String entNum) {
            return aliasMap.get(entNum);
        }
    }

    Spring IoC 的依赖注入

    这里我想再谈一下上面的第二个步骤,第二个步骤的核心就是通过Spring IoC依赖注入的特性,实现了策略实现类的注册过程(这一步自己实现会需要很多工作,并且代码不会很好看)。

    实际上除了Map这种变量类型,Spring 还能给List 变量进行自动装配。比如下面的代码。

    @Component
    public class EntStrategyHolder {
    
        @Autowired
        private Map<String, EntStrategy> entStrategyMap;
    
        @Autowired
        private List<EntStrategy> entStrategyList;
    
        public EntStrategy getBy(String entNum) {
            return entStrategyMap.get(entNum);
        }
    
        public void print() {
            System.out.println("===== implementation Map =====");
            System.out.println(entStrategyMap);
            entStrategyMap.forEach((name, impl)-> {
                System.out.println(name + ":" + impl.getStuff());
            });
            System.out.println("===== implementation List =====");
            System.out.println(entStrategyList);
            entStrategyList.forEach(impl-> System.out.println(impl.getStuff()));
        }
    }

    启动类

    @Configuration
    @ComponentScan("ric.study.demo.ioc.autowire_all_implementation_demo_set")
    public class Boostrap {
    
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Boostrap.class);
            context.getBean(EntStrategyHolder.class).print();
        }
    }

    输出结果

    ===== implementation Map =====
    {defaultStrategy=其他企业, entAStrategy=企业A, entBStrategy=企业B}
    defaultStrategy:其他企业
    entAStrategy:企业A
    entBStrategy:企业B
    ===== implementation List =====
    [其他企业, 企业A, 企业B]
    其他企业
    企业A
    企业B

     可以看到entStrategyList被成功赋值了。

    只不过这个特性我暂时没有找到应用场景,所以单独拿出来说一下。

  • 相关阅读:
    【线段树】【积累】主席树杂题积累 2016CCPC长春K SequenceII
    【积累】最小不能表示正整数 (以及一些做法
    【字符串】回文树&&回文自动机PAM
    【字符串】后缀自动机SAM
    【字符串】AC自动机
    【字符串】Trie树
    StringUtils类中isEmpty与isBlank的区别
    【Git】pull遇到错误:error: Your local changes to the following files would be overwritten by merge:
    jsp 与jstl
    listener 作用
  • 原文地址:https://www.cnblogs.com/sxw123/p/14046220.html
Copyright © 2011-2022 走看看