. 沒人看的前言
枚舉相信大家都不陌生,在日常的開發中,我們在大多數情況下使用枚舉一般是為了羅列既定的屬性值,作用其實與常量差別不大,但枚舉的優勢在於,可以定義多種類型的多個常量,自由度和擴展度會大大高於普通常量,而且閱讀起來會比常量更加直觀,因為枚舉內的屬性不一定全部都要用到,一般在定義枚舉時都會添加一個注釋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和發送方法類,這么做的一大好處,就是更好的擴展性,如果新增渠道,只需要新增枚舉和方法,而不需要改動原有方法,這在開發中是很重要的。