使用設(shè)計模式,干掉 if else 語句

場景

1. 場景介紹

公司在做一個物聯(lián)卡相關(guān)的項目時,需要對多個卡供應商(移動、聯(lián)通、電信的下級卡片代理商,公司已經(jīng)接入了5家卡片供應商)的接口進行調(diào)用,我負責將不同供應商的接口進行整合,形成一個統(tǒng)一的調(diào)用接口,大概的流程如下。


image

比較常見的實現(xiàn)方式是通過if - else if - else來進行不同供應商的選擇,但是使用if-else 時,隨著供應商的不斷增多,else if的代碼會越來越多,越來越復雜,不利于擴展,違反了開閉原則(對擴展開放,對修改關(guān)閉)

2. 其他類似場景

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

2.2 接入多種加密方式

2.3 接入多種登錄方式

2.4 接入多家物流公司接口

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

使用工廠方法模式+模板方法模式,感覺我的代碼實現(xiàn)和這兩個設(shè)計模式比較接近,代碼結(jié)構(gòu)如下。


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(全類名,通過反射創(chuàng)建實例)

esim:
  suppliers:
    1:
      name: 中國移動
      class-name: com.gnl.auth.test.service.SupplierYiDong
    2:
      name: 中國聯(lián)通
      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. 添加供應商工廠類并注入

添加工廠類

方法一:通過配置文件和反射拿到所有供應商實現(xiàn)類

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;

    /**
     * 構(gòu)造函數(shù)
     *
     * @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 {
                // 使用反射創(chuàng)建供應商服務對象,并設(shè)置進map中
                Class<?> classBook = Class.forName(supplier.getValue().getClassName());
                EsimSupplier esimSupplier = (EsimSupplier) classBook.newInstance();
                // 設(shè)置供應商信息
                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. 添加不同供應商實現(xiàn)類

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 中國聯(lián)通

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("聯(lián)通卡查詢成功: {}", JSON.toJSONString(info));
        EsimSupplierProp.SupplierInfo supplierInfo = this.getSupplierInfo();
        log.info("聯(lián)通配置: {}", JSON.toJSONString(supplierInfo));
        return info;
    }

    @Override
    public Boolean stop(Esim esim) {
        log.info("聯(lián)通卡停機成功: {}", esim.getEsimId());
        return true;
    }

    @Override
    public Boolean reply(Esim esim) {
        log.info("聯(lián)通卡復機成功: {}", 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();
        // 聯(lián)通
//        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 測試結(jié)果

中國移動


image

中國聯(lián)通


image

中國電信


image

7. 拓展新供應商

拓展新的供應商只需要兩個步驟,第一個步驟:添加配置,第二個步驟:實現(xiàn)供應商接口,具體如下。

7.1 添加配置文件

image

7.2 實現(xiàn)供應商接口

繼承EsimSupplier,重寫抽象類里的方法即可。


image

8. EsimSupplier的實現(xiàn)類中使用SpringBean方法

http://www.itdecent.cn/p/8b3864623a42

9.之后學到的更優(yōu)雅的辦法

9.1 供應商實現(xiàn)類都加上@Component,注冊為SpringBean

9.2 創(chuàng)建獲取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 創(chuàng)建工廠類

import vip.gnloypp.test.svc.config.SpringUtil;
import vip.gnloypp.test.svc.service.EsimSupplier;

import java.util.Map;

public class EsimSupplierFactory {

    // 獲取所有 EsimSupplier 的實現(xiàn)類
    private static Map<String, EsimSupplier> beansMap;
    static {
        // 通過SpringBean工具類獲取所有 EsimSupplier 的實現(xiàn)類
        beansMap = SpringUtil.getBeansOfType(EsimSupplier.class);
    }

    // 根據(jù)實現(xiàn)類beanName獲取實現(xiàn)類
    public static EsimSupplier getEsimSupplier(String esimSupplierName) {
        EsimSupplier esimSupplier = beansMap.get(esimSupplierName);
        if(esimSupplier == null){
            throw new RuntimeException("供應商不存在");
        }
        return esimSupplier;
    }
}

9.4 查詢esim卡信息獲取相應的供應商beanName,得到供應商實現(xiàn)類

EsimSupplier supplierYiDong= EsimSupplierFactory.getEsimSupplier("supplierYiDong");
EsimSupplier supplierDianXin= EsimSupplierFactory.getEsimSupplier("supplierDianXin");
EsimSupplier supplierLianTong= EsimSupplierFactory.getEsimSupplier("supplierLianTong");
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容