枚举
枚举是个好东西,用得好,可以极大简化代码,提高程序扩展性!
当系统中需要使用一些常量时,一般都会采用全局静态常量来定义这些值。
但是这样做的弊端是,常量与常量之间没有任何关联关系。
比喻现在定义了一组静态常量:
public static final String elongCode = "300802";
public static final String elongName = "艺龙";
public static final String elongHost = "www.elong.com";
public static final String qunarCode = "300803";
public static final String qunarName = "去哪儿";
public static final String qunarHost = "www.qunar.com";
在知道一个常量300802之后,需要与之相关的常量值code name host,普通的静态常量肯定无法满足。
枚举类型应运而生。
public enum Platform{
elong("300802", "艺龙", "www.elong.com"),
quner("300803", "去哪儿", "www.qunar.com"),
ly("300804", "同程", "www.ly.com"),
;
public String code;
public String name;
public String host;
Platform (String code, String name, String host) {
this.code = code;
this.name = name;
this.host = host;
}
private static Map<String, Platform> map = new HashMap<>();
static {
Platform[] ens = Platform.values();
for(Platform en : ens){
map.put(en.code, en);
}
}
public static Platform getEnumByCode(String code){
return map.get(code);
}
}
现在,可以通过Platform.getEnumByCode("300802"),获取到elong枚举值,然后和elong相关联的code name host,全部可以通过elong枚举值获取到。
即使以后新加了平台,只需要在枚举中,新增一个平台枚举值即可,其他取值的地方,不需要做任何修改!
案例1:化简代码
//改签状态;1 2 3 4 5 6 7 8 9 10 11 12 13 14
if("1".equals(textValue)){
return "已申请";
}else if("2".equals(textValue)){
return "已调度";
}else if("3".equals(textValue)){
return "已复核";
}else if("4".equals(textValue)){
return "等待订座";
}else if("5".equals(textValue)){
return "已订座";
}else if("6".equals(textValue)){
return "等待办理";
}else if("7".equals(textValue)){
return "改签失败";
}else if("8".equals(textValue)){
return "已改签";
}else if("9".equals(textValue)){
return "已完成";
}else if("10".equals(textValue)){
return "客户消";
}else if("11".equals(textValue)){
return "系统消";
}else if("12".equals(textValue)){
return "订座失败";
}else if("13".equals(textValue)){
return "系统取消中";
}else if("14".equals(textValue)){
return "客户取消中";
}else {
return null;
}
根据改签状态的值,转换成状态说明,这是一种最笨的方法,无法扩展、无法复用!
方案1-1
使用Map,将改签状态的值作为key,对应的状态说明作为value,存入到Map中;
private final static Map<String, String> map = new HashMap() {
{
this.put("1", "已申请");
this.put("2", "已调度");
this.put("3", "已复核");
this.put("4", "等待订座");
this.put("5", "已订座");
this.put("6", "等待办理");
this.put("7", "改签失败");
this.put("8", "已改签");
this.put("9", "已完成");
this.put("10", "客户消");
this.put("11", "系统消");
this.put("12", "订座失败");
this.put("13", "系统取消中");
this.put("14", "客户取消中");
}
};
String result = map.get(textValue); //获取状态说明
这段代码比案例1要好一些,但是依然无法扩展和复用。
比喻,在知道改签状态后,需要判断如果为1 2 6 7 12,则可以取消改签;否则不可以取消;
那么还需要写判断逻辑:
if( ",1,2,6,7,12,".indexOf("," + textValue + ",") > -1 ){
return true;
} else {
return false;
}
这个方法写成公共的,供其他地方调用,貌似没有毛病。
但是,如果业务发生变动,还需要加一种状态:15 - 取消失败;取消失败的订单,还可以继续取消。
在map中增加一条map.put("15", "取消失败");。状态值没问题了,但是,判断取消的逻辑如果忘记、或者遗漏了修改,那么取消功能就出现bug了!(系统一旦复杂起来,时间一久,你无法确认自己当初写的代码在哪里;或者别人交接的代码分布在哪里;加一种状态,需要全系统、前后端一齐检查的复杂工作!)
另外,在知道改签状态后,判断哪些状态下可以支付?哪些可以退款?判断逻辑更加复杂!
方案1-2
状态的文字说明、该状态下是否可以取消、是否可以支付、是否可以退款,这一类数据、数值,全部和改签状态有关联;在改签状态确定后,那么文字说明、能否取消、能否支付、能否退款,这个都是固定不变的!
使用枚举类,来定义这些改签状态
public enum ChangeStatus{
//状态码 状态码描述 取消 支付 退款
ysq("1", "已申请", true, true, true),
ydd("2", "已调度", true, true, true),
yfh("3", "已复核", false, true, true),
dddz("4", "等待订座", false, true, true),
ydz("5", "已订座", false, true, false),
ddbl("6", "等待办理", true, true, true),
gqsb("7", "改签失败", true, true, true),
ygq("8", "已改签", false, true, false),
ywc("9", "已完成", false, true, false),
khx("10", "客户消", false, false, true),
xtx("11", "系统消", false, false, true),
dzsb("12", "订座失败", true, true, true),
xtqxz("13", "系统取消中", false, false, true),
khqxz("14", "客户取消中", false, false, true),
;
public String code; //状态码
public String desc; //状态码描述
public boolean cancel; //取消
public boolean pay; //支付
public boolean refund; //退款
ChangeStatus(String code, String desc, boolean cancel, boolean pay, boolean refund) {
this.code = code;
this.desc = desc;
this.cancel = cancel;
this.pay = pay;
this.refund = refund;
}
private static Map<String, ChangeStatus> map = new HashMap<>(32);
static {
ChangeStatus[] ens = ChangeStatus.values();
for ( ChangeStatus en : ens ) {
map.put(en.code, en);
}
}
public static ChangeStatus getEnum(String orderStatus) {
return map.get(orderStatus);
}
}
通过ChangeStatus.getEnum(orderStatus)获取到指定改签状态的枚举值,再通过枚举值,可以获取到文字说明、能否取消、能否支付、能否退款信息;
如果需要需要添加15 - 取消失败,只需要增加一组枚举值,而且在新增枚举时,必须同时指定枚举的cancel pay refund值,避免发生遗漏修改的地方、
案例2:解耦合
之前介绍到,可以通过SpringContextUtil配合interfere,不用反射,实现动态调用字符串指向的方法,现在换成枚举和interfere来实现:
现在有若干个平台,通过平台编号,区分调用哪个平台的api接口:传入入参、执行方法、得到返回结果;
可以每个平台,视为一个对象,对象里面有调用平台的账号密码等属性;
调用平台的api接口,可以抽象成一个interfere,因为所有的平台都有这个传入入参、执行方法、得到返回结果的功能;平台类,实现interfere,实现抽象方法;
根据传入的平台编号,得到对应的平台对象,使用interfere (平台对象的父类) 接收,然后执行interfere的方法,等价执行具体实现类的方法;
具体实现
平台api interfere,提供 postSend 抽象方法
/**
* 平台api interfere,提供 postSend 抽象方法
* */
public interface PlatformApi{
/**
* 执行平台api接口,输入系统入参,和系统返回数据模型的class
* @param myReq 系统入参
* @param respClazz 系统响应的class类型
* @return T of respClazz
* */
public <T> T postSend(MyReq myReq, Class<T> respClazz);
}
平台类实现interfere,实现抽象方法
// 艺龙平台类
public class ElongImpl implements PlatformApi{
@Override
public <T> T postSend (MyReq myReq, Class<T> respClazz) {
ElongRequest elongReq = convertReq(myReq);//转换系统入参为平台入参
System.out.println(elongReq.getElongApi());
return convertResp(new ElongResponse());//转换平台响应为系统响应
}
}
// 艺龙api入参对象
public class ElongRequest {
private String elongApi = "艺龙";
public String getElongApi () {
return elongApi;
}
public void setElongApi (String elongApi) {
this.elongApi = elongApi;
}
}
// 艺龙api响应对象
public class ElongResponse {
}
// 去哪儿平台类
public class QunarImpl implements PlatformApi{
@Override
public <T> T postSend (MyReq myReq, Class<T> respClazz) {
QunarRequest qunarReq = convertReq(myReq);//转换系统入参为平台入参
System.out.println(qunarReq.getQunarApi());
return convertResp(new QunarResponse());//转换平台响应为系统响应
}
}
// 去哪儿api入参对象
class QunarRequest {
private String qunarApi = "去哪儿";
public String getQunarApi () {
return qunarApi;
}
public void setQunarApi (String qunarApi) {
this.qunarApi = qunarApi;
}
}
// 去哪儿api响应对象
public class QunarResponse {
}
定义平台枚举
public enum Platform{
elong("300802", "艺龙", ElongImpl.class),
quner("300803", "去哪儿", QunarImpl.class),
;
public String code;
public String name;
public PlatformApi apiImpl;
Platform (String code, String name, Class<? extends PlatformApi> apiClazz) {
this.code = code;
this.name = name;
try {
/**
* 入参:Class<? extends PlatformApi> apiClazz,代表apiClazz对应的类,一定是继承了PlatformApi
* apiClazz.newInstance => 通过class创建对象。
* 而apiClazz对应的类,继承了PlatformApi,因此通过class创建的对象,也是一定属于PlatformApi的子类
* ElongImpl QunarImpl都实现了PlatformApi,因此可以使用PlatformApi来接收具体实现类
* 只在枚举初始化执行一次,所以实例化的对象都是单例!在此次实例化apiImpl之后,还可以做一些初始化工作
* */
this.apiImpl = apiClazz.newInstance();
} catch ( Exception e ) {
e.printStackTrace();
}
}
private static Map<String, Platform> map = new HashMap<>();
static {
Platform[] ens = Platform.values();
for(Platform en : ens){
map.put(en.code, en);
}
}
public static Platform getEnumByCode(String code){
return map.get(code);
}
}
主方法
Platform platform = Platform.getEnumByCode("300802");
//其他地方传入、或组装的参数
MyReq myReq = new MyReq();
// ...
//执行平台api方法,并且返回平台响应类型的对象
MyResp myResp = platform.apiImpl.postSend(myReq, MyResp.class);
// platform.apiImpl 是单例,使用时注意线程安全
增加一个平台,也只需要在Platform增加一组枚举,对应新加一组平台类、平台入参、平台响应类,主方法代码块不需要任何修改!如果平台有多个api接口需要对接,比喻PlatformApi里面还有queryOrderInfo、queryOrderList方法。可以将MyReq类进行改造:所有的系统入参,都实现一个interfere
/**
* 系统入参都应实现改interfere,实现
* */
public interface BaseMyReq{
/**
* 返回平台编号。eg:300801 || 300802
*/
public String thePlatform();
/**
* 执行平台api接口
* @param platformApi 平台PlatformApi的实现类
* @param respClazz 系统响应的class类型
* @return T of respClazz
* */
public <T> T doSend(PlatformApi platformApi, Class<T> respClazz);
}
具体系统入参
/**
* 系统入参,查询订单
* */
public class QueryOrderInfo implements BaseMyReq{
private String orderId;
private String platform;
public String getOrderId(){
return this.orderId;
}
public String setOrderId(String orderId){
this.orderId = orderId;
}
public String getPlatform(){
return this.platform;
}
public String setPlatform(String platform){
this.platform = platform;
}
/**
* 返回平台编号。eg:300801 || 300802
* */
public String thePlatform(){
return this.platform;
}
/**
* 执行平台api接口
* @param platformApi 平台PlatformApi的实现类
* @param respClazz 系统响应的class类型
* @return T of respClazz
* */
public <T> T doSend(PlatformApi platformApi, Class<T> respClazz){
return platformApi.queryOrderInfo(this, respClazz);
}
// 同理,如果系统入参QueryOrderList,那么就执行 platformApi.queryOrderList(this, respClazz);
}
主方法
//......
//其他地方传入、或组装的参数,XXXX代表具体的系统入参对象
BaseMyReq myReq = new XXXX();
myReq.setPlatform("300801");//判断调用哪个平台
MyResp myResp = postApi(myReq );
/**
* 核心转发代码
* 所有的平台、所有的api都执行这个方法,此处可以记录系统输入输出、平台输入输出日志;添加监控;装饰增强等
* */
public <T> T postApi(BaseMyReq myReq, Class<T> respClazz){
String platformCode = myReq.thePlatform();//得到平台代码
Platform platform = Platform.getEnumByCode(platformCode);//通过平台代码获取到平台枚举
PlatformApi platformApi = platform.apiImpl;//得到平台执行类
return myReq.doSend(platformApi, respClazz);//执行平台的指定api方法
}
到第7步后,PlatformApi 和具体的平台执行类之间,可以增加一层抽象类,实现缺省适配。
同样增加一个平台,也只需要在Platform增加一组枚举,对应新加一组平台类、平台入参、平台响应类,主方法代码块不需要任何修改!
增加一个平台api接口,需要增加一个BaseMyReq子类,对应的平台中实现api对接代码即可。