优雅代码 - 善用Enum

235 阅读4分钟

优雅代码汇总篇

Enum真的是个好东西,香。

用enum + spring实现策略模式

在实际的项目中,一定会碰到很多需要需要横向扩展的场景,比如:技术方面的,Dubbo的负载均衡策略,协议策略,序列化策略等等。业务方面的,支付策略(支付宝,微信,余额),登录策略(用户名密码,验证码)。这些场景都是在一定范围内可以枚举,并且需要有横向扩展的能力,需要符合开闭原则。

Dubbo用了自己的SPI,目的是为开发者做二次开发留下很好的扩展口,这方面知识可以参考文档:Dubbo SPI。系统开发或者业务开发,没有必要提供二次开发扩展口的情况下,可以用Enum来做到比较优雅的实现。

处理方式:

Enum作为策略的列表枚举

Springbean工场可以替代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种用途,关于这部分,只是我的个人经验。能解决问题的方法才是好方法。