場景
1. 場景介紹
公司在做一個物聯(lián)卡相關(guān)的項目時,需要對多個卡供應(yīng)商(移動镐牺、聯(lián)通炫掐、電信的下級卡片代理商,公司已經(jīng)接入了5家卡片供應(yīng)商)的接口進行調(diào)用睬涧,我負責將不同供應(yīng)商的接口進行整合募胃,形成一個統(tǒng)一的調(diào)用接口,大概的流程如下畦浓。
image
比較常見的實現(xiàn)方式是通過if - else if - else來進行不同供應(yīng)商的選擇摔认,但是使用if-else 時,隨著供應(yīng)商的不斷增多宅粥,else if的代碼會越來越多,越來越復(fù)雜参袱,不利于擴展,違反了開閉原則(對擴展開放秽梅,對修改關(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 添加配置文件
配置供應(yīng)商key(supplierId)、name(供應(yīng)商名稱)钞诡、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. 添加供應(yīng)商模板方法抽象類
import com.gnl.auth.test.config.EsimSupplierProp;
import com.gnl.auth.test.entity.Esim;
import lombok.Data;
@Data
public abstract class EsimSupplier {
/**
* 供應(yīng)商信息
*/
private EsimSupplierProp.SupplierInfo supplierInfo;
/**
* 卡信息
*/
public abstract Esim info(Esim esim);
/**
* 停機
*/
public abstract Boolean stop(Esim esim);
/**
* 復(fù)機
*/
public abstract Boolean reply(Esim esim);
/**
* 卡充值
*/
public Boolean recharge(Esim esim) {
throw new RuntimeException(supplierInfo.getName() + "不支持充值操作");
}
}
4. 添加供應(yīng)商工廠類并注入
添加工廠類
方法一:通過配置文件和反射拿到所有供應(yīng)商實現(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 {
/**
* 供應(yīng)商服務(wù)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)建供應(yīng)商服務(wù)對象,并設(shè)置進map中
Class<?> classBook = Class.forName(supplier.getValue().getClassName());
EsimSupplier esimSupplier = (EsimSupplier) classBook.newInstance();
// 設(shè)置供應(yīng)商信息
esimSupplier.setSupplierInfo(supplier.getValue());
esimSupplierMap.put(supplier.getKey(), esimSupplier);
} catch (ClassNotFoundException e) {
throw new RuntimeException(supplier.getValue().getName() + "供應(yīng)商不存在", e);
} catch (IllegalAccessException e) {
throw new RuntimeException(supplier.getValue().getName() + "供應(yīng)商不存在", e);
} catch (InstantiationException e) {
throw new RuntimeException(supplier.getValue().getName() + "供應(yīng)商不存在", e);
}
}
}
/**
* 獲取供應(yīng)商服務(wù)
*/
public EsimSupplier getEsimSupplier(Long supplierId) {
EsimSupplier esimSupplier = esimSupplierMap.get(supplierId);
if (esimSupplier == null) {
throw new RuntimeException("卡供應(yīng)商不存在");
}
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. 添加不同供應(yīng)商實現(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("移動卡復(fù)機成功: {}", 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)通卡復(fù)機成功: {}", 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("電信卡復(fù)機成功: {}", 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. 拓展新供應(yīng)商
拓展新的供應(yīng)商只需要兩個步驟荧降,第一個步驟:添加配置接箫,第二個步驟:實現(xiàn)供應(yīng)商接口,具體如下朵诫。
7.1 添加配置文件
image
7.2 實現(xiàn)供應(yīng)商接口
繼承EsimSupplier辛友,重寫抽象類里的方法即可。
image
8. EsimSupplier的實現(xiàn)類中使用SpringBean方法
http://www.reibang.com/p/8b3864623a42
9.之后學(xué)到的更優(yōu)雅的辦法
9.1 供應(yīng)商實現(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("供應(yīng)商不存在");
}
return esimSupplier;
}
}
9.4 查詢esim卡信息獲取相應(yīng)的供應(yīng)商beanName废累,得到供應(yīng)商實現(xiàn)類
EsimSupplier supplierYiDong= EsimSupplierFactory.getEsimSupplier("supplierYiDong");
EsimSupplier supplierDianXin= EsimSupplierFactory.getEsimSupplier("supplierDianXin");
EsimSupplier supplierLianTong= EsimSupplierFactory.getEsimSupplier("supplierLianTong");