满屏的if-else,看我怎么消灭你!
今日推薦
最適合晚上睡不著看的 8 個網站,建議收藏哦
文章來源:http://u6.gg/k376d
在實際的業務開發當中,經常會遇到復雜的業務邏輯,可能部分同學實現出來的代碼并沒有什么問題,但是代碼的可讀性很差。
本篇文章主要總結一下自己在實際開發中如何避免大面積的 if-else 代碼塊的問題。補充說明一點,不是說 if-else 不好,而是多層嵌套的 if-else 導致代碼可讀性差、維護成本高等問題。
現有如下一段示例代碼,部分優化技巧是根據這段代碼進行的:
技巧一:提取方法,拆分邏輯?
比如上面這段代碼中:
if(null?!=?city)?{}?else?{}這里可以拆分成兩段邏輯,核心思想就是邏輯單元最小化,然后合并邏輯單元。
private?void?getCityNotNull(Integer?city,?List<TestCodeData>?newDataList)?{if?(newDataList?!=?null?&&?newDataList.size()?>?0)?{TestCodeData?newData?=?newDataList.stream().filter(p?->?{if?(p.getIsHoliday()?==?1)?{return?true;}return?false;}).findFirst().orElse(null);if?(newData?!=?null)?{newData.setCity(city);}} }//?合并邏輯流程 private?void?getBadCodeBiz(Integer?city,?List<TestCodeData>?newDataList,?List<TestCodeData>?oldDataList)?{if?(city?!=?null)?{this.getCityNull(city,?newDataList);}?else?{//此處代碼省略} }技巧二:分支邏輯提前return?
比如“技巧一”中的 getCityNull 方法,我們可以這樣寫:
public?void?getCityNotNull(Integer?city,?List<TestCodeData>?newDataList)?{if?(CollectionUtils.isEmpty(newDataList))?{//?提前判斷,返回業務邏輯return;}TestCodeData?newData?=?newDataList.stream().filter(p?->?{if?(p.getIsHoliday()?==?1)?{return?true;}return?false;}).findFirst().orElse(null);if?(null?!=?newData)?{newData.setCity(city);} }技巧三:枚舉?
經過“技巧一”和“技巧二”的優化,文章開頭的這段代碼被優化成如下所示:
public?class?BadCodeDemo?{public?void?getBadCodeBiz(Integer?city,?List<TestCodeData>?newDataList,?List<TestCodeData>?oldDataList)?{if?(city?!=?null)?{this.getCityNotNull(city,?newDataList);}?else?{this.getCityNull(newDataList,?oldDataList);}}private?void?getCityNotNull(Integer?city,?List<TestCodeData>?newDataList)?{if?(CollectionUtils.isEmpty(newDataList))?{//?提前判斷,返回業務邏輯return;}TestCodeData?newData?=?newDataList.stream().filter(p?->?{if?(p.getIsHoliday()?==?1)?{return?true;}return?false;}).findFirst().orElse(null);if?(null?!=?newData)?{newData.setCity(city);}}private?void?getCityNull(List<TestCodeData>?newDataList,?List<TestCodeData>?oldDataList)?{//?提前判斷,返回業務邏輯if?(CollectionUtils.isEmpty(oldDataList)?&&?CollectionUtils.isEmpty(newDataList))?{return;}List<TestCodeData>?oldCollect?=?oldDataList.stream().filter(p?->?{if?(p.getIsHoliday()?==?1)?{return?true;}return?false;}).collect(Collectors.toList());List<TestCodeData>?newCollect?=?newDataList.stream().filter(p?->?{if?(p.getIsHoliday()?==?1)?{return?true;}return?false;}).collect(Collectors.toList());//?提前判斷,返回業務邏輯if?(CollectionUtils.isEmpty(newCollect)?&&?CollectionUtils.isEmpty(oldCollect))?{return;}for?(TestCodeData?newPO?:?newCollect)?{if?(newPO.getStartTime()?==?0?&&?newPO.getEndTime()?==?12)?{TestCodeData?po?=?oldCollect.stream().filter(p?->?p.getStartTime()?==?0&&?(p.getEndTime()?==?12?||?p.getEndTime()?==?24)).findFirst().orElse(null);if?(po?!=?null)?{newPO.setCity(po.getCity());}}?else?if?(newPO.getStartTime()?==?12?&&?newPO.getEndTime()?==?24)?{TestCodeData?po?=?oldCollect.stream().filter(p?->?(p.getStartTime()?==?12?||?p.getStartTime()?==?0)&&?p.getEndTime()?==?24).findFirst().orElse(null);if?(po?!=?null)?{newPO.setCity(po.getCity());}}?else?if?(newPO.getStartTime()?==?0?&&?newPO.getEndTime()?==?24)?{TestCodeData?po?=?oldCollect.stream().filter(p?->?p.getStartTime()?==?0?&&?p.getEndTime()?==?24).findFirst().orElse(null);if?(po?==?null)?{po?=?oldCollect.stream().filter(p?->?p.getStartTime()?==?0?&&?p.getEndTime()?==?12).findFirst().orElse(null);}if?(po?==?null)?{po?=?oldCollect.stream().filter(p?->?p.getStartTime()?==?12?&&?p.getEndTime()?==?24).findFirst().orElse(null);}if?(po?!=?null)?{newPO.setCity(po.getCity());}}?else?if?(newPO.getTimeUnit().equals(Integer.valueOf(1)))?{TestCodeData?po?=?oldCollect.stream().filter(e?->?e.getTimeUnit().equals(Integer.valueOf(1))).findFirst().orElse(null);if?(po?!=?null)?{newPO.setCity(po.getCity());}}}} }現在利用“枚舉”來優化 getCityNull 方法中的 for 循環部分代碼,我們可以看到這段代碼中有 4 段邏輯,總體形式如下:
if?(newPO.getStartTime()?==?0?&&?newPO.getEndTime()?==?12)?{//第一段邏輯 }?else?if?(newPO.getStartTime()?==?12?&&?newPO.getEndTime()?==?24)?{//第二段邏輯 }?else?if?(newPO.getStartTime()?==?0?&&?newPO.getEndTime()?==?24)?{//第三段邏輯 }?else?if?(newPO.getTimeUnit().equals(Integer.valueOf(1)))?{//第四段邏輯 }按照這個思路利用枚舉進行二次優化,將其中的邏輯封裝到枚舉類中:
public?enum?TimeEnum?{AM("am",?"上午")?{@Overridepublic?void?setCity(TestCodeData?data,?List<TestCodeData>?oldDataList)?{TestCodeData?po?=?oldDataList.stream().filter(p?->?p.getStartTime()?==?0&&?(p.getEndTime()?==?12?||?p.getEndTime()?==?24)).findFirst().orElse(null);if?(null?!=?po)?{data.setCity(po.getCity());}}},PM("pm",?"下午")?{@Overridepublic?void?setCity(TestCodeData?data,?List<TestCodeData>?oldCollect)?{TestCodeData?po?=?oldCollect.stream().filter(p?->?(p.getStartTime()?==?12?||?p.getStartTime()?==?0)&&?p.getEndTime()?==?24).findFirst().orElse(null);if?(po?!=?null)?{data.setCity(po.getCity());}}},DAY("day",?"全天")?{@Overridepublic?void?setCity(TestCodeData?data,?List<TestCodeData>?oldCollect)?{TestCodeData?po?=?oldCollect.stream().filter(p?->?p.getStartTime()?==?0?&&?p.getEndTime()?==?24).findFirst().orElse(null);if?(po?==?null)?{po?=?oldCollect.stream().filter(p?->?p.getStartTime()?==?0?&&?p.getEndTime()?==?12).findFirst().orElse(null);}if?(po?==?null)?{po?=?oldCollect.stream().filter(p?->?p.getStartTime()?==?12?&&?p.getEndTime()?==?24).findFirst().orElse(null);}if?(po?!=?null)?{data.setCity(po.getCity());}}},HOUR("hour",?"小時")?{@Overridepublic?void?setCity(TestCodeData?data,?List<TestCodeData>?oldCollect)?{TestCodeData?po?=?oldCollect.stream().filter(e?->?e.getTimeUnit().equals(Integer.valueOf(1))).findFirst().orElse(null);if?(po?!=?null)?{data.setCity(po.getCity());}}};public?abstract?void?setCity(TestCodeData?data,?List<TestCodeData>?oldCollect);private?String?code;private?String?desc;TimeEnum(String?code,?String?desc)?{this.code?=?code;this.desc?=?desc;}public?String?getCode()?{return?code;}public?void?setCode(String?code)?{this.code?=?code;}public?String?getDesc()?{return?desc;}public?void?setDesc(String?desc)?{this.desc?=?desc;} }然后 getCityNull 方法中 for 循環部分邏輯如下:
for?(TestCodeData?data?:?newCollect)?{if?(data.getStartTime()?==?0?&&?data.getEndTime()?==?12)?{TimeEnum.AM.setCity(data,?oldCollect);}?else?if?(data.getStartTime()?==?12?&&?data.getEndTime()?==?24)?{TimeEnum.PM.setCity(data,?oldCollect);}?else?if?(data.getStartTime()?==?0?&&?data.getEndTime()?==?24)?{TimeEnum.DAY.setCity(data,?oldCollect);}?else?if?(data.getTimeUnit().equals(Integer.valueOf(1)))?{TimeEnum.HOUR.setCity(data,?oldCollect);} }其實在這個業務場景中使用枚舉并不是特別合適,如果在遍歷對象時,我們就知道要執行哪個枚舉類型,此時最合適,偽代碼如下:
for?(TestCodeData?data?:?newCollect)?{String?code?=?"am";??//?這里假設?code?變量是從?data?中獲取的TimeEnum.valueOf(code).setCity(data,?oldCollect); }技巧四:函數式接口?
業務場景描述:比如讓你做一個簡單的營銷拉新活動,這個活動投放到不同的渠道,不同渠道過來的用戶獎勵不一樣。
現假設在頭條、微信等渠道都投放了該活動。此時你的代碼可能會寫出如下形式:
@RestController @RequestMapping("/activity") public?class?ActivityController?{@Resourceprivate?AwardService?awardService;@PostMapping("/reward")public?void?reward(String?userId,?String?source)?{if?("toutiao".equals(source))?{awardService.toutiaoReward(userId);}?else?if?("wx".equals(source))?{awardService.wxReward(userId);}} }@Service public?class?AwardService?{private?static?final?Logger?log?=?LoggerFactory.getLogger(AwardService.class);public?Boolean?toutiaoReward(String?userId)?{log.info("頭條渠道用戶{}獎勵50元紅包!",?userId);return?Boolean.TRUE;}public?Boolean?wxReward(String?userId)?{log.info("微信渠道用戶{}獎勵100元紅包!",?userId);return?Boolean.TRUE;} }看完這段代碼,邏輯上是沒有什么問題的。但它有一個隱藏的缺陷,如果后期又增加很多渠道的時候,你該怎么辦?繼續 else if 嗎?
其實我們可以利用函數式接口優化,當然設計模式也可以優化。這里我只是舉例使用一下函數式接口的使用方式。
@RestController @RequestMapping("/activity") public?class?ActivityController?{@Resourceprivate?AwardService?awardService;@PostMapping("/reward")public?void?reward(String?userId,?String?source)?{awardService.getRewardResult(userId,?source);} }@Service public?class?AwardService?{private?static?final?Logger?log?=?LoggerFactory.getLogger(AwardService.class);private?Map<String,?BiFunction<String,?String,?Boolean>>?sourceMap?=?new?HashMap<>();@PostConstructprivate?void?dispatcher()?{sourceMap.put("wx",?(userId,?source)?->?this.wxReward(userId));sourceMap.put("toutiao",?(userId,?source)?->?this.toutiaoReward(userId));}public?Boolean?getRewardResult(String?userId,?String?source)?{BiFunction<String,?String,?Boolean>?result?=?sourceMap.get(source);if?(null?!=?result)?{return?result.apply(userId,?source);}return?Boolean.FALSE;}private?Boolean?toutiaoReward(String?userId)?{log.info("頭條渠道用戶{}獎勵50元紅包!",?userId);return?Boolean.TRUE;}private?Boolean?wxReward(String?userId)?{log.info("微信渠道用戶{}獎勵100元紅包!",?userId);return?Boolean.TRUE;} }針對一些復雜的業務場景,業務參數很多時,可以利用 @FunctionalInterface 自定義函數式接口來滿足你的業務需求,使用原理和本例并無差別。
技巧五:設計模式?
設計模式對于 if-else 的優化,我個人覺得有些重,但是也是一種優化方式。設計模式適合使用在大的業務流程和場景中使用,針對代碼塊中的 if-else 邏輯優化不推薦使用。
常用的設計模式有:
策略模式
模板方法
工廠模式
單例模式
還是以上面的營銷拉新活動為例來說明如何使用。
| 使用技巧一:工廠模式+抽象類
定義抽象業務接口:
public?abstract?class?AwardAbstract?{public?abstract?Boolean?award(String?userId); }定義具體業務實現類:
//?頭條渠道發放獎勵業務 public?class?TouTiaoAwardService?extends?AwardAbstract?{@Overridepublic?Boolean?award(String?userId)?{log.info("頭條渠道用戶{}獎勵50元紅包!",?userId);return?Boolean.TRUE;} }//?微信渠道發放獎勵業務 public?class?WeChatAwardService?extends?AwardAbstract?{@Overridepublic?Boolean?award(String?userId)?{log.info("微信渠道用戶{}獎勵100元紅包!",?userId);return?Boolean.TRUE;} }利用工廠模式獲取實例對象:
public?class?AwardFactory?{public?static?AwardAbstract?getAwardInstance(String?source)?{if?("toutiao".equals(source))?{return?new?TouTiaoAwardService();}?else?if?("wx".equals(source))?{return?new?WeChatAwardService();}return?null;} }業務入口處根據不同渠道執行不同的發放邏輯:
@PostMapping("/reward2") public?void?reward2(String?userId,?String?source)?{AwardAbstract?instance?=?AwardFactory.getAwardInstance(source);if?(null?!=?instance)?{instance.award(userId);} }| 使用技巧二:策略模式+模板方法+工廠模式+單例模式
還是以營銷拉新為業務場景來說明,這個業務流程再增加一些復雜度,比如發放獎勵之前要進行身份驗證、風控驗證等一些列的校驗,此時你的業務流程該如何實現更清晰簡潔呢?
定義業務策略接口:
/**?策略業務接口?*/ public?interface?AwardStrategy?{/***?獎勵發放接口*/Map<String,?Boolean>?awardStrategy(String?userId);/***?獲取策略標識,即不同渠道的來源標識*/String?getSource(); }定義獎勵發放模板流程:
public?abstract?class?BaseAwardTemplate?{private?static?final?Logger?log?=?LoggerFactory.getLogger(BaseAwardTemplate.class);//獎勵發放模板方法public?Boolean?awardTemplate(String?userId)?{this.authentication(userId);this.risk(userId);return?this.awardRecord(userId);}//身份驗證protected?void?authentication(String?userId)?{log.info("{}?執行身份驗證!",?userId);}//風控protected?void?risk(String?userId)?{log.info("{}?執行風控校驗!",?userId);}//執行獎勵發放protected?abstract?Boolean?awardRecord(String?userId); }實現不同渠道的獎勵業務:
@Slf4j @Service public?class?ToutiaoAwardStrategyService?extends?BaseAwardTemplate?implements?AwardStrategy?{/***?獎勵發放接口*/@Overridepublic?Boolean?awardStrategy(String?userId)?{return?super.awardTemplate(userId);}@Overridepublic?String?getSource()?{return?"toutiao";}/***?具體的業務獎勵發放實現*/@Overrideprotected?Boolean?awardRecord(String?userId)?{log.info("頭條渠道用戶{}獎勵50元紅包!",?userId);return?Boolean.TRUE;} }@Slf4j @Service public?class?WeChatAwardStrategyService?extends?BaseAwardTemplate?implements?AwardStrategy?{/***?獎勵發放接口*/@Overridepublic?Boolean?awardStrategy(String?userId)?{return?super.awardTemplate(userId);}@Overridepublic?String?getSource()?{return?"wx";}/****?具體的業務獎勵發放實現*/@Overrideprotected?Boolean?awardRecord(String?userId)?{log.info("微信渠道用戶{}獎勵100元紅包!",?userId);return?Boolean.TRUE;} }定義工廠方法,對外統一暴露業務調用入口:
@Component public?class?AwardStrategyFactory?implements?ApplicationContextAware?{private?final?static?Map<String,?AwardStrategy>?MAP?=?new?HashMap<>();@Overridepublic?void?setApplicationContext(ApplicationContext?applicationContext)?throws?BeansException?{Map<String,?AwardStrategy>?beanTypeMap?=?applicationContext.getBeansOfType(AwardStrategy.class);beanTypeMap.values().forEach(strategyObj?->?MAP.put(strategyObj.getSource(),?strategyObj));}/***?對外統一暴露的工廠方法*/public?Boolean?getAwardResult(String?userId,?String?source)?{AwardStrategy?strategy?=?MAP.get(source);if?(Objects.isNull(strategy))?{throw?new?RuntimeException("渠道異常!");}return?strategy.awardStrategy(userId);}/***?靜態內部類創建單例工廠對象*/private?static?class?CreateFactorySingleton?{private?static?AwardStrategyFactory?factory?=?new?AwardStrategyFactory();}public?static?AwardStrategyFactory?getInstance()?{return?CreateFactorySingleton.factory;} }業務入口方法:
@RestController @RequestMapping("/activity") public?class?ActivityController?{@PostMapping("/reward3")public?void?reward3(String?userId,?String?source)?{AwardStrategyFactory.getInstance().getAwardResult(userId,?source);} }假如發起請求:POST http://localhost:8080/activity/reward3?userId=fei&source=wx
2022-02-20?12:23:27.716??INFO?20769?---?[nio-8080-exec-1]?c.a.c.e.o.c.p.s.BaseAwardTemplate????????:?fei?執行身份驗證! 2022-02-20?12:23:27.719??INFO?20769?---?[nio-8080-exec-1]?c.a.c.e.o.c.p.s.BaseAwardTemplate????????:?fei?執行風控校驗! 2022-02-20?12:23:27.719??INFO?20769?---?[nio-8080-exec-1]?a.c.e.o.c.p.s.WeChatAwardStrategyService?:?微信渠道用戶fei獎勵100元紅包!其他技巧?
其他技巧:
使用三目運算符
相同業務邏輯提取復用
寫在最后?
不論使用那種技巧,首先是我們在業務代碼開發過程中一定要多思考,將復雜的業務邏輯能通過簡潔的代碼表現出來,這才是你的核心能力之一,而不是一個 curd boy。與君共勉,共同進步!
最后,再給大家推薦一個GitHub項目,該項目整理了上千本常用技術PDF,技術書籍都可以在這里找到。
GitHub地址:https://github.com/hello-go-maker/cs-books
電子書已經更新好了,拿走不謝,記得點一個star,持續更新中...總結
以上是生活随笔為你收集整理的满屏的if-else,看我怎么消灭你!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 重磅!阿里发布全新操作系统,这次要干翻
- 下一篇: 使用 kafka 提升你的订单接口吞吐量