美文网首页
使用设计模式,干掉 if else 语句

使用设计模式,干掉 if else 语句

作者: 扮鬼之梦 | 来源:发表于2021-01-25 15:49 被阅读0次

    场景

    1. 场景介绍

    公司在做一个物联卡相关的项目时,需要对多个卡供应商(移动、联通、电信的下级卡片代理商,公司已经接入了5家卡片供应商)的接口进行调用,我负责将不同供应商的接口进行整合,形成一个统一的调用接口,大概的流程如下。


    image

    比较常见的实现方式是通过if - else if - else来进行不同供应商的选择,但是使用if-else 时,随着供应商的不断增多,else if的代码会越来越多,越来越复杂,不利于扩展,违反了开闭原则(对扩展开放,对修改关闭)

    2. 其他类似场景

    2.1 接入多家三方支付公司接口

    2.2 接入多种加密方式

    2.3 接入多种登录方式

    2.4 接入多家物流公司接口

    3. 如何干掉 if-else 语句?

    使用工厂方法模式+模板方法模式,感觉我的代码实现和这两个设计模式比较接近,代码结构如下。


    image

    代码

    1. 配置

    1.1 添加配置类

    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    import java.util.Map;
    
    @Data
    @Component
    @ConfigurationProperties(prefix = "esim")
    public class EsimSupplierProp {
    
        private Map<Long, SupplierInfo> suppliers;
    
        @Data
        public static class SupplierInfo {
            private String name;
            private String className;
        }
    
    }
    

    1.2 添加配置文件

    配置供应商key(supplierId)、name(供应商名称)、class-name(全类名,通过反射创建实例)

    esim:
      suppliers:
        1:
          name: 中国移动
          class-name: com.gnl.auth.test.service.SupplierYiDong
        2:
          name: 中国联通
          class-name: com.gnl.auth.test.service.SupplierLianTong
        3:
          name: 中国电信
          class-name: com.gnl.auth.test.service.SupplierDianXin
    

    2. 添加实体类

    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.math.BigDecimal;
    
    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    public class Esim {
        private Long esimId;
        private Long esimSupplierId;
        private String msisdn;
        private BigDecimal packagePrice;
        private BigDecimal packageCapacity;
    }
    

    3. 添加供应商模板方法抽象类

    import com.gnl.auth.test.config.EsimSupplierProp;
    import com.gnl.auth.test.entity.Esim;
    import lombok.Data;
    
    @Data
    public abstract class EsimSupplier {
    
        /**
         * 供应商信息
         */
        private EsimSupplierProp.SupplierInfo supplierInfo;
    
        /**
         * 卡信息
         */
        public abstract Esim info(Esim esim);
    
        /**
         * 停机
         */
        public abstract Boolean stop(Esim esim);
    
        /**
         * 复机
         */
        public abstract Boolean reply(Esim esim);
    
        /**
         * 卡充值
         */
        public Boolean recharge(Esim esim) {
            throw new RuntimeException(supplierInfo.getName() + "不支持充值操作");
        }
    
    }
    

    4. 添加供应商工厂类并注入

    添加工厂类

    方法一:通过配置文件和反射拿到所有供应商实现类

    import com.gnl.auth.test.config.EsimSupplierProp;
    import com.gnl.auth.test.service.EsimSupplier;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class EsimSupplierFactory {
        /**
         * 供应商服务map
         */
        private Map<Long, EsimSupplier> esimSupplierMap;
    
        /**
         * 构造函数
         *
         * @param esimSupplierProp 配置文件
         */
        public EsimSupplierFactory(EsimSupplierProp esimSupplierProp) {
            // 初始化map
            this.esimSupplierMap = new HashMap<>();
            // 遍历配置文件
            Map<Long, EsimSupplierProp.SupplierInfo> suppliers = esimSupplierProp.getSuppliers();
            for (Map.Entry<Long, EsimSupplierProp.SupplierInfo> supplier : suppliers.entrySet()) {
                try {
                    // 使用反射创建供应商服务对象,并设置进map中
                    Class<?> classBook = Class.forName(supplier.getValue().getClassName());
                    EsimSupplier esimSupplier = (EsimSupplier) classBook.newInstance();
                    // 设置供应商信息
                    esimSupplier.setSupplierInfo(supplier.getValue());
                    esimSupplierMap.put(supplier.getKey(), esimSupplier);
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(supplier.getValue().getName() + "供应商不存在", e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(supplier.getValue().getName() + "供应商不存在", e);
                } catch (InstantiationException e) {
                    throw new RuntimeException(supplier.getValue().getName() + "供应商不存在", e);
                }
            }
        }
    
        /**
         * 获取供应商服务
         */
        public EsimSupplier getEsimSupplier(Long supplierId) {
            EsimSupplier esimSupplier = esimSupplierMap.get(supplierId);
            if (esimSupplier == null) {
                throw new RuntimeException("卡供应商不存在");
            }
            return esimSupplier;
        }
    
    }
    

    注入工厂类

    import com.gnl.auth.test.factory.EsimSupplierFactory;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class EsimSupplierConfig {
        /**
         * 注入工厂
         */
        @Bean
        public EsimSupplierFactory EsimSupplierFactory(EsimSupplierProp esimSupplierProp) {
            return new EsimSupplierFactory(esimSupplierProp);
        }
    }
    

    5. 添加不同供应商实现类

    5.1 中国移动

    import com.alibaba.fastjson.JSON;
    import com.gnl.auth.test.config.EsimSupplierProp;
    import com.gnl.auth.test.entity.Esim;
    import lombok.extern.log4j.Log4j2;
    
    import java.math.BigDecimal;
    
    @Log4j2
    public class SupplierYiDong extends EsimSupplier {
        @Override
        public Esim info(Esim esim) {
            Esim info = Esim.builder()
                    .esimId(esim.getEsimId())
                    .esimSupplierId(1L)
                    .msisdn("1800000000" + esim.getEsimId())
                    .packageCapacity(new BigDecimal(2048))
                    .packagePrice(new BigDecimal(22))
                    .build();
            log.info("移动卡查询成功: {}", JSON.toJSONString(info));
            EsimSupplierProp.SupplierInfo supplierInfo = this.getSupplierInfo();
            log.info("移动配置: {}",JSON.toJSONString(supplierInfo));
            return info;
        }
    
        @Override
        public Boolean stop(Esim esim) {
            log.info("移动卡停机成功: {}", esim.getEsimId());
            return true;
        }
    
        @Override
        public Boolean reply(Esim esim) {
            log.info("移动卡复机成功: {}", esim.getEsimId());
            return true;
        }
    }
    

    5.1 中国联通

    import com.alibaba.fastjson.JSON;
    import com.gnl.auth.test.config.EsimSupplierProp;
    import com.gnl.auth.test.entity.Esim;
    import lombok.extern.log4j.Log4j2;
    
    import java.math.BigDecimal;
    
    @Log4j2
    public class SupplierLianTong extends EsimSupplier {
        @Override
        public Esim info(Esim esim) {
            Esim info = Esim.builder()
                    .esimId(esim.getEsimId())
                    .esimSupplierId(2L)
                    .msisdn("1800000000" + esim.getEsimId())
                    .packageCapacity(new BigDecimal(3072))
                    .packagePrice(new BigDecimal(33))
                    .build();
            log.info("联通卡查询成功: {}", JSON.toJSONString(info));
            EsimSupplierProp.SupplierInfo supplierInfo = this.getSupplierInfo();
            log.info("联通配置: {}", JSON.toJSONString(supplierInfo));
            return info;
        }
    
        @Override
        public Boolean stop(Esim esim) {
            log.info("联通卡停机成功: {}", esim.getEsimId());
            return true;
        }
    
        @Override
        public Boolean reply(Esim esim) {
            log.info("联通卡复机成功: {}", esim.getEsimId());
            return true;
        }
    }
    

    5.1 中国电信

    import com.alibaba.fastjson.JSON;
    import com.gnl.auth.test.config.EsimSupplierProp;
    import com.gnl.auth.test.entity.Esim;
    import lombok.extern.log4j.Log4j2;
    
    import java.math.BigDecimal;
    
    @Log4j2
    public class SupplierDianXin extends EsimSupplier {
    
        @Override
        public Esim info(Esim esim) {
            Esim info = Esim.builder()
                    .esimId(esim.getEsimId())
                    .esimSupplierId(3L)
                    .msisdn("1800000000" + esim.getEsimId())
                    .packageCapacity(new BigDecimal(6144))
                    .packagePrice(new BigDecimal(66))
                    .build();
            log.info("电信卡查询成功: " + JSON.toJSONString(info));
            EsimSupplierProp.SupplierInfo supplierInfo = this.getSupplierInfo();
            log.info("电信配置: {}",JSON.toJSONString(supplierInfo));
            return info;
        }
    
        @Override
        public Boolean stop(Esim esim) {
            log.info("电信卡停机成功: {}", esim.getEsimId());
            return true;
        }
    
        @Override
        public Boolean reply(Esim esim) {
            log.info("电信卡复机成功: {}", esim.getEsimId());
            return true;
        }
    
        public Boolean recharge(Esim esim){
            log.info("电信卡充值成功: {}", esim.getEsimId());
            return true;
        }
    }
    

    6. 测试

    6.1 添加测试类

    import com.gnl.auth.test.entity.Esim;
    import com.gnl.auth.test.factory.EsimSupplierFactory;
    import com.gnl.auth.test.service.EsimSupplier;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    @SpringBootTest
    public class EsimTest {
    
        @Autowired
        private EsimSupplierFactory esimSupplierFactory;
    
        @Test
        void test1() {
            // 移动
            Esim esim = Esim.builder().esimId(1L).esimSupplierId(1l).build();
            // 联通
    //        Esim esim = Esim.builder().esimId(2L).esimSupplierId(2l).build();
            // 电信
    //        Esim esim = Esim.builder().esimId(3L).esimSupplierId(3l).build();
    
            EsimSupplier esimSupplier = esimSupplierFactory.getEsimSupplier(esim.getEsimSupplierId());
    
            esimSupplier.info(esim);
            esimSupplier.stop(esim);
            esimSupplier.reply(esim);
            esimSupplier.recharge(esim);
        }
    
    }
    

    6.2 测试结果

    中国移动


    image

    中国联通


    image

    中国电信


    image

    7. 拓展新供应商

    拓展新的供应商只需要两个步骤,第一个步骤:添加配置,第二个步骤:实现供应商接口,具体如下。

    7.1 添加配置文件

    image

    7.2 实现供应商接口

    继承EsimSupplier,重写抽象类里的方法即可。


    image

    8. EsimSupplier的实现类中使用SpringBean方法

    https://www.jianshu.com/p/8b3864623a42

    9.之后学到的更优雅的办法

    9.1 供应商实现类都加上@Component,注册为SpringBean

    9.2 创建获取SpringBean的工具类

    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.stereotype.Component;
    
    import java.util.Map;
    
    @Component
    public class SpringUtil implements ApplicationContextAware {
    
        private static ApplicationContext applicationContext;
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            if (SpringUtil.applicationContext == null) {
                SpringUtil.applicationContext = applicationContext;
            }
        }
    
        //获取applicationContext
        public static ApplicationContext getApplicationContext() {
            return applicationContext;
        }
    
        //通过name获取 Bean.
        public static Object getBean(String name) {
            return applicationContext.getBean(name);
        }
    
        //通过class获取Bean.
        public static <T> T getBean(Class<T> clazz) {
            return applicationContext.getBean(clazz);
        }
    
        //通过name,以及Clazz返回指定的Bean
        public static <T> T getBean(String name, Class<T> clazz) {
            return applicationContext.getBean(name, clazz);
        }
    
        //通过Class返回指定的BeanMap
        public static <T> Map<String, T> getBeansOfType(Class<T> clazz) {
            return applicationContext.getBeansOfType(clazz);
        }
    }
    

    9.3 创建工厂类

    import vip.gnloypp.test.svc.config.SpringUtil;
    import vip.gnloypp.test.svc.service.EsimSupplier;
    
    import java.util.Map;
    
    public class EsimSupplierFactory {
    
        // 获取所有 EsimSupplier 的实现类
        private static Map<String, EsimSupplier> beansMap;
        static {
            // 通过SpringBean工具类获取所有 EsimSupplier 的实现类
            beansMap = SpringUtil.getBeansOfType(EsimSupplier.class);
        }
    
        // 根据实现类beanName获取实现类
        public static EsimSupplier getEsimSupplier(String esimSupplierName) {
            EsimSupplier esimSupplier = beansMap.get(esimSupplierName);
            if(esimSupplier == null){
                throw new RuntimeException("供应商不存在");
            }
            return esimSupplier;
        }
    }
    

    9.4 查询esim卡信息获取相应的供应商beanName,得到供应商实现类

    EsimSupplier supplierYiDong= EsimSupplierFactory.getEsimSupplier("supplierYiDong");
    EsimSupplier supplierDianXin= EsimSupplierFactory.getEsimSupplier("supplierDianXin");
    EsimSupplier supplierLianTong= EsimSupplierFactory.getEsimSupplier("supplierLianTong");
    

    相关文章

      网友评论

          本文标题:使用设计模式,干掉 if else 语句

          本文链接:https://www.haomeiwen.com/subject/aqcizktx.html