Enum真的是个好东西,香。
用enum + spring实现策略模式
在实际的项目中,一定会碰到很多需要需要横向扩展的场景,比如:技术方面的,Dubbo的负载均衡策略,协议策略,序列化策略等等。业务方面的,支付策略(支付宝,微信,余额),登录策略(用户名密码,验证码)。这些场景都是在一定范围内可以枚举,并且需要有横向扩展的能力,需要符合开闭原则。
Dubbo用了自己的SPI,目的是为开发者做二次开发留下很好的扩展口,这方面知识可以参考文档:Dubbo SPI。系统开发或者业务开发,没有必要提供二次开发扩展口的情况下,可以用Enum来做到比较优雅的实现。
处理方式:
① Enum作为策略的列表枚举
② Spring的bean工场可以替代SPI作为策略实现类动态获取的工具。
下面分别以Dubbo的负载策略和支付策略为案例说明下这种方式。
Dubbo的负载均衡策略
// 负载均衡,接口
public interface LoadBalance {
// 从多个待选执行列表中选出一个
Invoker select(List<Invoker> invokers);
}
// 负载均衡策略,参照dubbo的4种。只是示意
public enum LoadBalanceStrategy {
CONSISTENT_HASH("consistentHashLoadBalance"),//一致性 hash 算法
LEAST_ACTIVE("leastActiveLoadBalance"),//最小活跃数负载均衡
RANDOM("randomLoadBalance"),//加权随机算法
ROUND_ROBIN("roundRobinLoadBalance"),//加权轮询负载均衡
;
private String beanId;
LoadBalanceType(String beanId) {
this.beanId = beanId;
}
public String getBeanId() {
return beanId;
}
}
// 四种实现方式,具体实现都省略了。只是表意。
public class RoundRobinLoadBalance implements LoadBalance {//加权轮询负载均衡
}
public class RandomLoadBalance implements LoadBalance {//加权随机算法
}
public class LeastActiveLoadBalance implements LoadBalance {//最小活跃数负载均衡
}
public class ConsistentHashLoadBalance implements LoadBalance {//一致性 hash 算法
}
// 这个类作为对其他模块暴露的入口
public class LoadBalanceDispatcher {
@Autowired
private ApplicationContext applicationContext;
public Invoker doSelect(LoadBalanceStrategy strategy, List<Invoker> invokers) {
// 通过applicationContext动态获取bean对象
LoadBalance loadBalance = applicationContext.getBean(strategy.getBeanId(), LoadBalance.class);
return loadBalance.select(invokers);
}
}
支付工具策略(我上个项目的真实代码)
public interface IPayProcessor {
// 生成支付订单
String createPayOrder(Order order, String userIp);
// 第三方支付结果的通知
boolean payStateNotice(Map<String, String> notifyMap);
// 查询支付订单
Integer queryPayOrder(PayOrder payOrder);
// 退款
boolean refundPayOrder(String payId);
// 关闭交易
boolean closePayOrder(String payId);
}
// 对,你没看错,我真的用了中文作为enum的value。
// 我们在适当的场景下,有这么做的必要性,别碰我。
public enum PAY_TOOL_TYPE {
支付宝app支付(1, "aliPayProcessor"),
微信app支付(2, "wxappPayProcessor"),
微信小程序支付(3, "wxmpPayProcessor"),
微信H5支付(4, "wxmwebPayProcessor"),
微信扫码付(7, "wxNativePayProcessor"),
微信快应用支付(8, "wxQuickAppPayProcessor"),
支付宝H5支付(5, "aliWapPayProcessor"),
支付宝扫码付(6, "aliScanCodePayProcessor"),
;
private int type;
private String beanId;
PAY_TOOL_TYPE(int type, String beanId) {
this.type = type;
this.beanId = beanId;
}
public int getType() {
return type;
}
public String getBeanId() {
return beanId;
}
}
上述案例是真实项目,我个人特别喜欢这种方式去使用Enum
用enum开定义错误码
阿里规约泰山版中定义了错误码列表。我之前很多项目都是用常量类来定义一堆错误码,并且加上中文注释,用的时候可以方便查看。类似于下面这样。
public class ErrorCodeConstant {
/**
* 一切 ok
*/
public static final String A0000 = "A0000";
/**
* 用户端错误
*/
public static final String A0001 = "A0001";
/**
* 用户注册错误
*/
public static final String A0002 = "A0002";
}
用时间长了,总感觉不舒服。在代码中看到的永远是一堆编号,得点进去才能看见中文解释。怎么能在代码中直接看见中文?并且在接口out出系统时转换成编码呢?用Enum。我在项目中尝试了,因为用了中文,称不上优雅,但很舒服。下面介绍下。
// 用Enum替代了常量类
// 使用了中文,在代码中不管是抛出点,还是拦截点,都能看到
// 我考虑过要使用英文,但是看到满屏的错误码,果断放弃了,如果真用了满屏的英文,还不如来点错误码数字了。。。
public enum EXCEPTION_MESSAGE {
交易成功("0000"),
系统错误("0001"),
请求参数有误("0002"),
用户token失效("0006"),
;
private String code;
EXCEPTION_MESSAGE(String code) {
this.code = code;
}
public String getCode() {
return code;
}
// 这个方法用来对比某个exception实例是否与当前这个相同
// 实际项目中用起来,可读性很高。看下面的演示
public boolean isMessage(MinifException e) {
return this.name().equals(e.getMessage());
}
}
// throw 异常的地方
throw new MinifException(EXCEPTION_MESSAGE.有座位已被抢_赶紧再选个座位下单吧);
try {
filmOrderService.filmLockSeat(orders, verticalOrderFilm, bestSeats);
} catch (MinifException e) {
// 做异常判断,并且转换。使用isMessage方法来判断。可读性高
// 都是直接来中文,容易理解和维护
if (EXCEPTION_MESSAGE.有座位已被抢_赶紧再选个座位下单吧.isMessage(e)) {
throw new MinifException(EXCEPTION_MESSAGE.有座位被抢重新选座);
}
if (EXCEPTION_MESSAGE.异常未释放的座位超出了最大值_请等待15分钟或主动释放座位.isMessage(e)) {
throw new MinifException(EXCEPTION_MESSAGE.未释放座位超出最大值);
}
throw new MinifException(EXCEPTION_MESSAGE.锁定座位失败);
}
对于Enmu,介绍了2种用途,关于这部分,只是我的个人经验。能解决问题的方法才是好方法。