枚举类的扩展使用
. 没人看的前言
枚举相信大家都不陌生,在日常的开发中,我们在大多数情况下使用枚举一般是为了罗列既定的属性值,作用其实与常量差别不大,但枚举的优势在于,可以定义多种类型的多个常量,自由度和扩展度会大大高于普通常量,而且阅读起来会比常量更加直观,因为枚举内的属性不一定全部都要用到,一般在定义枚举时都会添加一个注释key,也就是此枚举值的说明字段。那么既然枚举可自由扩展,在开发中,我们就可以利用枚举来减少繁琐的代码步骤,甚至解决某些难题。
1.使用枚举扩展属性
举个常见的例子:
Excel导出统计数据,其中,Excel内容包括标题、子标题、内容,标题、子标题格式已知
Excel导出,封装数据时,很多人习惯直接在方法体里拼接,这么做后患无穷,最大的两个影响:可读性差、扩展性差,我们可能经常会需要客户该改需求的情况,如果客户提出想要在Excel里加个字段或者换下位置,可能会让人头大。
那么利用枚举,我们可以很好的解决这点。先看枚举实例:
其中,每个枚举值代表一个标题,xxxHead为子标题,xxxField为内容字段,如此一来,Excel的内容便有了一个初步的概图,接下来写入数据也就清晰明了了
/** * @description:分析数据Excel数据枚举 */ public enum AnalysisExcelDataType { DATE("date","/", dataInEnum.dateHead, dataInEnum.dateField), SHOP("shop","店铺首页", dataInEnum.shopHead, dataInEnum.shopField), GOODS("goods","商品页", dataInEnum.goodsHead, dataInEnum.goodsField), PAGE("page","落地页面", dataInEnum.pageHead, dataInEnum.pageField), CUSTOMER("customer","客户行为", dataInEnum.customerHead, dataInEnum.customerField), ORDER("order","下单", dataInEnum.orderHead, dataInEnum.orderField), PAY("pay","付款", dataInEnum.payHead, dataInEnum.payField), COUPON("coupon","优惠券", dataInEnum.couponHead, dataInEnum.couponField); private String title; private String titleName; private String[] headStrData; private String[] fieldStrData; AnalysisExcelDataType(String title, String titleName, String[] headStrData, String[] fieldStrData){ this.title = title; this.titleName = titleName; this.headStrData = headStrData; this.fieldStrData = fieldStrData; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getTitleName() { return titleName; } public void setTitleName(String titleName) { this.titleName = titleName; } public String[] getHeadStrData() { return headStrData; } public void setHeadStrData(String[] headStrData) { this.headStrData = headStrData; } public String[] getFieldStrData() { return fieldStrData; } public void setFieldStrData(String[] fieldStrData) { this.fieldStrData = fieldStrData; } } class dataInEnum{ public static final String[] dateHead = {"日期"}; public static final String[] dateField = {"dataTime"}; public static final String[] shopHead = {"PV","UV"}; public static final String[] shopField = {"shopPvNum","shopUvNum"}; public static final String[] goodsHead = {"PV","UV"}; public static final String[] goodsField = {"skuPvNum","skuUvNum"}; public static final String[] pageHead = {"PV","UV"}; public static final String[] pageField = {"pv","uv"}; public static final String[] customerHead = {"加购商品人数","收藏商品人数","收藏店铺人数"}; public static final String[] customerField = {"cartNum","followSkuNum","followShopNum"}; public static final String[] orderHead = {"下单人数","下单金额","下单订单数","下单件数"}; public static final String[] orderField = {"ordPins","ordAmount","ordNum","ordQtty"}; public static final String[] payHead = {"付款人数", "付款金额", "付款订单数", "付款件数"}; public static final String[] payField = {"payPins","payAmount","payNum","payQtty"}; public static final String[] couponHead = {"领券人数", "用券人数", "引入订单量", "引入金额"}; public static final String[] couponField = {"couponPins","couponUsePins","couponOrders","couponAmount"}; }
枚举创建好后,开始写入数据,这里为了可读性和方便(主要~),将标题与内容分开写入,先看标题:
/** *Excel标题、子标题写入 */ private static LinkedHashMap createTitle(HSSFSheet sheet){ String[] head = null; String[] field = null; //标题 LinkedHashMap<String, String> headMap = new LinkedHashMap<>(); //子标题 LinkedHashMap<String, String> headMap2 = new LinkedHashMap<>(); List<Integer> colspanList = new ArrayList<>(); int cloCount = -1; //遍历标题枚举 for(AnalysisExcelDataType dataType : AnalysisExcelDataType.values()){ head = dataType.getHeadStrData(); field = dataType.getFieldStrData(); for(int i=0;i<head.length;i++){ headMap2.put(field[i], head[i]); } headMap.put(dataType.getTitle(), dataType.getTitleName()); if(head.length > 1){ //标题跨行 每个标题跨的行数为子标题个数 sheet.addMergedRegion( new CellRangeAddress(0,0,cloCount+1, cloCount + head.length)); } colspanList.add(head.length); cloCount += head.length; } //样式 HSSFCellStyle style = ExcelExtUtil.createCellStyle(true, true,(short)11, null, HorizontalAlignment.CENTER); //标题 ExcelExtUtil.writeSheetTitle(sheet, headMap, 0, colspanList, style); style = ExcelExtUtil.createCellStyle(true, true,null, null, HorizontalAlignment.CENTER); //子标题 ExcelExtUtil.writeSheetTitle(sheet, headMap2, 1, null,style); return headMap2; }
这里使用了poi进行写入,有部分代码因为考虑篇幅没有贴出来,毕竟不是本文重点,如果有需要可以留言。
好,标题写入完毕,开始写入数据,方式与标题大同小异,利用循环,根据field查询数据内的字段值
这里的JSONArray就是普通的po集合
private void createSumExcelData(JSONArray dataArray){ String[] head = null; String[] field = null; List<Map<String, Object>> mapList = new ArrayList<>(); Map<String, Object> map = null; JSONObject jsonObject = null; HSSFSheet sheet = ExcelExtUtil.createSheet("统计数据"); //标题数据 同上 LinkedHashMap<String, String> headMap = createTitle(sheet); for(Object object : dataArray){ map = new HashMap<>(); for(AnalysisExcelDataType dataType : AnalysisExcelDataType.values()){ head = dataType.getHeadStrData(); field = dataType.getFieldStrData(); for(int i=0;i<head.length;i++){ //根据标题对应字段获取数据中的值 jsonObject = JSONObject.parseObject(JSONObject.toJSONString(object)); map.put(field[i], jsonObject.get(field[i])); } } mapList.add(map); } HSSFCellStyle style = ExcelExtUtil.createCellStyle(false, true,null, null, HorizontalAlignment.CENTER); //数据 ExcelExtUtil.writeSheetData(sheet, headMap, mapList, 2, style); }
如此一来,如果Excel需要变动,那么只需要改动枚举即可,数据封装主体完全不需要改动,是不是很优雅了呢~~
2.枚举配合多态
话不多说,让我们先看一个需求例子:
现需要开发一个消息通知工具类,需要透出统一发送方法和单一发送方法,并支持多渠道消息通知,且渠道间的入参有差异
我的做法是使用多态,继承关系来实现多渠道消息发送,并提供统一调用入口。
暂定两个发送渠道:短信、邮件,其中,Dto类如下:
import lombok.Data; /** * @author :shenzhikui * @description:渠道参数传输对象父类 * @date :2019/8/12 */ //lombok 自动生成getter、setter、toString @Data public abstract class BaseNotifyDto { private String recipient; //接收人 private String content; //内容 }
import lombok.Data; /** * @author :shenzhikui * @description:短信消息dto * @date :2019/8/12 */ @Data public class SmsNotifyDto extends BaseNotifyDto { private String signature; }
import lombok.Data; /** * @author :shenzhikui * @description:邮件消息dto * @date :2019/8/12 */ @Data public class EmailNotifyDto extends BaseNotifyDto { private String subject; }
然后,定义消息发送工具类,如下:
import java.util.*; /** * @author :shenzhikui * @description:消息发送父类 * @date :2019/8/12 */ public abstract class BaseMessageNotify { /** * 初始化 */ protected abstract void init(); /** * 发送消息 */ protected abstract void send(BaseNotifyDto baseNotifyDto); }
import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; /** * @author: shenzhikui * @description: 邮件消息通知类 * @date: 2019/8/12 */ @Slf4j @Data public class EmailNotify extends BaseMessageNotify { //发送者账号 private final String MAIL_SENDER = PropertiesUtil.getProperty(PropertiesConstant.NOTIFY_EMAIL_SENDER); @Autowired private JavaMailSender javaMailSender; @Override protected void init() { //... } /** * @author: shenzhikui * @description: 发送邮件 * @date: 2019/8/12 * @param baseNotifyVo * @return void */ @Override public void send(BaseNotifyDto baseNotifyVo) { try { EmailNotifyDto emailNotifyVo = (EmailNotifyDto)baseNotifyVo; SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); //邮件发送人 simpleMailMessage.setFrom(MAIL_SENDER); //邮件接收人 simpleMailMessage.setTo(emailNotifyVo.getRecipient()); //邮件主题 simpleMailMessage.setSubject(emailNotifyVo.getSubject()); //邮件内容 simpleMailMessage.setText(emailNotifyVo.getContent()); javaMailSender.send(simpleMailMessage); }catch (Exception e){ log.error("邮件发送失败", e.getMessage()); throw new RuntimeException(e); } } }
/** * @author: shenzhikui * @description: 消息通知服务类 * @date: 2019/8/12 */ public class SmsNotify extends BaseMessageNotify { @Override protected void init() { //... } @Override protected void send(BaseNotifyDto baseNotifyVo) { try { SmsNotifyDto smsNotifyVo = (SmsNotifyDto) baseNotifyVo; //由于短信调用方法涉及隐私,所以这里就不展示了,这里不是重点~ }catch (Exception e){ } } }
ok,到这里,工具类与dto都准备好了,然后提供调用方法,这里,如果使用常规方法(当然肯定有大佬有更好的方法,这里只是举例,轻喷~),其中一个例子,如下:
/** * 单个发送方法 */ public static void sendOne(BaseNotifyDto baseNotifyDto){ if(baseNotifyDto instanceof SmsNotifyDto){ new SmsNotify().send(baseNotifyDto); }else if(baseNotifyDto instanceof EmailNotifyDto){ new EmailNotify().send(baseNotifyDto); } } /** * 多渠道发送方法 */ public static void sendAll(List<BaseNotifyDto> baseNotifyDtoList){ for(BaseNotifyDto baseNotifyDto : baseNotifyDtoList){ if(baseNotifyDto instanceof SmsNotifyDto){ new SmsNotify().send(baseNotifyDto); } if(baseNotifyDto instanceof EmailNotifyDto){ new EmailNotify().send(baseNotifyDto); } } }
这种方法,不易扩展,并且存在冗余,而且极不优雅~。
这个时候,枚举的作用就来了,我们定义一个枚举:
其中,id为渠道值,发送渠道如果支持自定义,那么存放在数据库的格式一般为"1,2,3"这种拼接格式的值
/** * @description: 通知渠道枚举 * @author: shenzhikui * @date: 2019/7/17 */ public enum NotifyChannelEnum { SMS("短信", "1", new SmsNotify(), new SmsNotifyDto()), EMAIL("邮箱", "2", new EmailNotify(), new EmailNotifyDto()), private String key; private String id; private BaseMessageNotify notifyType; private BaseNotifyDto dtoType; public String getKey() { return key; } public String getId() { return id; } public BaseMessageNotify getNotifyType() { return notifyType; } public BaseNotifyDto getDtoType() { return dtoType; } NotifyChannelEnum(String key, String id, BaseMessageNotify notifyType, BaseNotifyDto dtoType) { this.key = key; this.id = id; this.notifyType = notifyType; this.dtoType = dtoType; } }
如此,我们根据此枚举来改造方法:
/** * @description: 发送指定渠道消息 */ public static void send(BaseNotifyDto baseNotifyDto){ createNotify(baseNotifyDto).send(baseNotifyDto); } /** * @description: 发送多渠道消息 */ public static void sendAll(List<BaseNotifyDto> notifyDtoList){ notifyDtoList.forEach( notifyDto -> createNotify(notifyDto).send(notifyDto)); } //*************************** 私有方法 ************************* private static BaseMessageNotify createNotify(BaseNotifyDto baseNotifyDto){ for(NotifyChannelEnum channelEnum : NotifyChannelEnum.values()){ if(channelEnum.getDtoType().getClass().equals(baseNotifyDto.getClass())){ return channelEnum.getNotifyType(); } } throw new RuntimeException("create messageNotify error----no notify"); }
好,新的方法更长了,此贴完结。。。。。。。。
开个玩笑,例子举的可能不是很好,但想要表达的意思还在,新的方法看似很长,但一劳永逸,枚举中每个属性都指定了渠道值,并且指定了渠道对应的dto和发送方法类,这么做的一大好处,就是更好的扩展性,如果新增渠道,只需要新增枚举和方法,而不需要改动原有方法,这在开发中是很重要的。