0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

為什么我不再推薦枚舉策略模式?

jf_ro2CN3Fa ? 來源:芋道源碼 ? 2023-04-14 10:52 ? 次閱讀


一、為什么講策略模式

策略模式,應(yīng)該是工作中比較常用的設(shè)計(jì)模式,調(diào)用方自己選擇用哪一種策略完成對(duì)數(shù)據(jù)的操作,也就是“一個(gè)類的行為或其算法可以在運(yùn)行時(shí)更改”

我個(gè)人的理解是 將一些除了過程不同其他都一樣的函數(shù)封裝成策略,然后調(diào)用方自己去選擇想讓數(shù)據(jù)執(zhí)行什么過程策略。常見的例子為根據(jù)用戶分類推薦不同的排行榜(用戶關(guān)注點(diǎn)不一樣,推薦榜單就不一樣)

和單例模式一樣,隨著時(shí)間發(fā)展,我不再推薦經(jīng)典策略模式,更推薦簡(jiǎn)單策略用枚舉策略模式,復(fù)雜地用工廠策略模式。下面引入一個(gè)例子,我們的需求是:對(duì)一份股票數(shù)據(jù)列表,給出低價(jià)榜、高價(jià)榜、漲幅榜。這其中只有排序條件的區(qū)別,比較適合作為策略模式的例子

基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能

  • 項(xiàng)目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 視頻教程:https://doc.iocoder.cn/video/

二、經(jīng)典策略模式

數(shù)據(jù)DTO

@Data
publicclassStock{

//股票交易代碼
privateStringcode;

//現(xiàn)價(jià)
privateDoubleprice;

//漲幅
privateDoublerise;
}

抽象得到的策略接口

publicinterfaceStrategy{

/**
*將股票列表排序
*
*@paramsource源數(shù)據(jù)
*@return排序后的榜單
*/
Listsort(Listsource);
}

實(shí)現(xiàn)我們的策略類

/**
*高價(jià)榜
*/
publicclassHighPriceRankimplementsStrategy{

@Override
publicListsort(Listsource){
returnsource.stream()
.sorted(Comparator.comparing(Stock::getPrice).reversed())
.collect(Collectors.toList());
}
}

/**
*低價(jià)榜
*/
publicclassLowPriceRankimplementsStrategy{

@Override
publicListsort(Listsource){
returnsource.stream()
.sorted(Comparator.comparing(Stock::getPrice))
.collect(Collectors.toList());
}
}

/**
*高漲幅榜
*/
publicclassHighRiseRankimplementsStrategy{

@Override
publicListsort(Listsource){
returnsource.stream()
.sorted(Comparator.comparing(Stock::getRise).reversed())
.collect(Collectors.toList());
}
}

經(jīng)典的Context類,

publicclassContext{
privateStrategystrategy;

publicvoidsetStrategy(Strategystrategy){
this.strategy=strategy;
}

publicListgetRank(Listsource){
returnstrategy.sort(source);
}
}

于是 我們順禮成章地得到調(diào)用類--榜單實(shí)例RankServiceImpl

@Service
publicclassRankServiceImpl{

/**
*dataService.getSource()提供原始的股票數(shù)據(jù)
*/
@Resource
privateDataServicedataService;

/**
*前端傳入榜單類型,返回排序完的榜單
*
*@paramrankType榜單類型
*@return榜單數(shù)據(jù)
*/
publicListgetRank(StringrankType){
//創(chuàng)建上下文
Contextcontext=newContext();
//這里選擇策略
switch(rankType){
case"HighPrice":
context.setStrategy(newHighPriceRank());
break;
case"LowPrice":
context.setStrategy(newLowPriceRank());
break;
case"HighRise":
context.setStrategy(newHighRiseRank());
break;
default:
thrownewIllegalArgumentException("rankTypenotfound");
}
//然后執(zhí)行策略
returncontext.getRank(dataService.getSource());
}
}

我們可以看到經(jīng)典方法,創(chuàng)建了一個(gè)接口、三個(gè)策略類,還是比較啰嗦的。調(diào)用類的實(shí)現(xiàn)也待商榷,新增一個(gè)策略類還要修改榜單實(shí)例(可以用抽象工廠解決,但是復(fù)雜度又上升了)。加之我們有更好的選擇,所以此處不再推薦經(jīng)典策略模式

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能

  • 項(xiàng)目地址:https://github.com/YunaiV/yudao-cloud
  • 視頻教程:https://doc.iocoder.cn/video/

三、基于枚舉的策略模式

這里對(duì)這種簡(jiǎn)單的策略,推薦用枚舉進(jìn)行優(yōu)化。枚舉的本質(zhì)是創(chuàng)建了一些靜態(tài)類的集合。

我下面直接給出例子,大家可以直觀感受一下

枚舉策略類

publicenumRankEnum{
//以下三個(gè)為策略實(shí)例
HighPrice{
@Override
publicListsort(Listsource){
returnsource.stream()
.sorted(Comparator.comparing(Stock::getPrice).reversed())
.collect(Collectors.toList());
}
},
LowPrice{
@Override
publicListsort(Listsource){
returnsource.stream()
.sorted(Comparator.comparing(Stock::getPrice))
.collect(Collectors.toList());
}
},
HighRise{
@Override
publicListsort(Listsource){
returnsource.stream()
.sorted(Comparator.comparing(Stock::getRise).reversed())
.collect(Collectors.toList());
}
};

//這里定義了策略接口
publicabstractListsort(Listsource);
}

對(duì)應(yīng)的調(diào)用類也得以優(yōu)化,榜單實(shí)例RankServiceImpl

@Service
publicclassRankServiceImpl{

/**
*dataService.getSource()提供原始的股票數(shù)據(jù)
*/
@Resource
privateDataServicedataService;

/**
*前端傳入榜單類型,返回排序完的榜單
*
*@paramrankType榜單類型形似RankEnum.HighPrice.name()
*@return榜單數(shù)據(jù)
*/
publicListgetRank(StringrankType){
//獲取策略,這里如果未匹配會(huì)拋IllegalArgumentException異常
RankEnumrank=RankEnum.valueOf(rankType);
//然后執(zhí)行策略
returnrank.sort(dataService.getSource());
}
}

可以看到,如果策略簡(jiǎn)單的話,基于枚舉的策略模式優(yōu)雅許多,調(diào)用方也做到了0修改,但正確地使用枚舉策略模式需要額外考慮以下幾點(diǎn)。

  • 枚舉的策略類是公用且靜態(tài),這意味著這個(gè)策略過程不能引入非靜態(tài)的部分,擴(kuò)展性受限
  • 策略模式的目標(biāo)之一,是優(yōu)秀的擴(kuò)展性和可維護(hù)性,最好能新增或修改某一策略類時(shí),對(duì)其他類是無(wú)改動(dòng)的。而枚舉策略如果過多或者過程復(fù)雜,維護(hù)是比較困難的,可維護(hù)性受限

四、基于工廠的策略模式

為了解決良好的擴(kuò)展性和可維護(hù)性,我更推薦以下利用spring自帶beanFactory的優(yōu)勢(shì),實(shí)現(xiàn)一個(gè)基于工廠的策略模式。

策略類改動(dòng)只是添加了@Service注解,并指定了Service的value屬性

/**
*高價(jià)榜
*注意申明Service.value=HighPrice,他是我們的key,下同
*/
@Service("HighPrice")
publicclassHighPriceRankimplementsStrategy{

@Override
publicListsort(Listsource){
returnsource.stream()
.sorted(Comparator.comparing(Stock::getPrice).reversed())
.collect(Collectors.toList());
}
}

/**
*低價(jià)榜
*/
@Service("LowPrice")
publicclassLowPriceRankimplementsStrategy{

@Override
publicListsort(Listsource){
returnsource.stream()
.sorted(Comparator.comparing(Stock::getPrice))
.collect(Collectors.toList());
}
}

/**
*高漲幅榜
*/
@Service("HighRise")
publicclassHighRiseRankimplementsStrategy{

@Override
publicListsort(Listsource){
returnsource.stream()
.sorted(Comparator.comparing(Stock::getRise).reversed())
.collect(Collectors.toList());
}
}

調(diào)用類修改較大,接入借助spring工廠特性,完成策略類

@Service
publicclassRankServiceImpl{

/**
*dataService.getSource()提供原始的股票數(shù)據(jù)
*/
@Resource
privateDataServicedataService;
/**
*利用注解@Resource@Autowired特性,直接獲取所有策略類
*key=@Service的value
*/
@Resource
privateMaprankMap;

/**
*前端傳入榜單類型,返回排序完的榜單
*
*@paramrankType榜單類型和Service注解的value屬性一致
*@return榜單數(shù)據(jù)
*/
publicListgetRank(StringrankType){
//判斷策略是否存在
if(!rankMap.containsKey(rankType)){
thrownewIllegalArgumentException("rankTypenotfound");
}
//獲得策略實(shí)例
Strategyrank=rankMap.get(rankType);
//執(zhí)行策略
returnrank.sort(dataService.getSource());
}
}

若讀者使用的不是Spring,也可以找找對(duì)應(yīng)框架的工廠模式實(shí)現(xiàn),或者自己實(shí)現(xiàn)一個(gè)抽象工廠。

工廠策略模式會(huì)比枚舉策略模式啰嗦,但也更加靈活、易擴(kuò)展性和易維護(hù)。故簡(jiǎn)單策略推薦枚舉策略模式,復(fù)雜策略才推薦工廠策略模式。



審核編輯 :李倩



聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 框架
    +關(guān)注

    關(guān)注

    0

    文章

    398

    瀏覽量

    17404
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4277

    瀏覽量

    62325

原文標(biāo)題:為什么我不再推薦枚舉策略模式?

文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    蘋果調(diào)整策略:逐步摒棄年更產(chǎn)品發(fā)布模式

    10月8日訊,彭博社知名記者馬克·古爾曼在《Power On》通訊中揭示,蘋果公司正逐步轉(zhuǎn)變其傳統(tǒng)的“年度更新”發(fā)布模式,邁向更為靈活的產(chǎn)品發(fā)布策略。
    的頭像 發(fā)表于 10-08 16:46 ?713次閱讀

    stm32f407 USB外接HUB怎么枚舉HUB和其他USB設(shè)備?

    stm32f407 的USB接口 想外接一個(gè)HUB 來支持更多的USB設(shè)備,但是這個(gè)HUB 該怎么枚舉,還有就是HUB下的USB設(shè)備怎么枚舉,有做過的大神嗎。求指導(dǎo),求demo。小弟拜謝了
    發(fā)表于 04-29 08:13

    STM32F103 USB枚舉不成功的原因?

    (NVIC_VectTab_FLASH,0x8800),但此時(shí)USB便枚舉不成功,此時(shí)其他中斷是正常的;但若把APP起始地址改為0X8000000不使用IAP進(jìn)行跳轉(zhuǎn)則枚舉沒有問題.比較懷疑中斷向量這塊的設(shè)置是不是還有問題,跪
    發(fā)表于 04-29 06:29

    stm32f103 usb枚舉問題求解

    移植了一個(gè)USB HID設(shè)備,發(fā)現(xiàn)必須初始化usart1,這樣usb hid設(shè)備才能枚舉,否則枚舉失敗,沒有發(fā)現(xiàn)硬件上有聯(lián)系啊?
    發(fā)表于 04-26 07:57

    CYUS3014 RAM燒寫成功,為什么枚舉不起來?

    CYUSB3014 RAM顯示燒寫成功后,缺不再枚舉,技術(shù)支持讓更換芯片后,問題依舊,請(qǐng)問是否還有別的方式可以查找問題?
    發(fā)表于 02-29 07:13

    cyusb3014枚舉的工作原理是什么?如果下載固件后不枚舉,可能會(huì)是什么原因,原理是什么?

    用3014設(shè)計(jì)自己的板子,參考開發(fā)板kit3的原理圖,插入計(jì)算機(jī)后可以被識(shí)別為Bootloader,采用USB啟動(dòng)模式,下載實(shí)例中的固件后,顯示燒寫成功,但設(shè)備在計(jì)算機(jī)中消失了,不再重新被
    發(fā)表于 02-29 07:11

    如何同時(shí)枚舉SlaveFIFO和UART(CDC)?

    使用Re: Slave FIFO + UART Driver Setup中的程序,將img下載進(jìn)FX3中,成功枚舉出了“USB串行設(shè)備(COM13)”,但是并沒有出現(xiàn)
    發(fā)表于 02-28 07:23

    如何在SlaveFifoSync例程中添加HID設(shè)備枚舉?

    您好,使用了SlaveFifoSync的32bit模式例程,現(xiàn)在想在其中添加一個(gè)HID的設(shè)備(鼠標(biāo)),程序見附件,參照了Can EZ-USB FX3 enumerate seri
    發(fā)表于 02-28 06:31

    cx3初始化GPIO會(huì)停止UVC枚舉的原因?

    需要使用 GPIO 為的圖像傳感器和其他設(shè)備進(jìn)行重置控制。 為此,正在嘗試在的 CSI 生成的代碼中添加簡(jiǎn)單的 GPIO 控制,但是當(dāng)我添加以下幾行時(shí),
    發(fā)表于 02-27 07:38

    cyusb3014進(jìn)入DP替代模式后斷開USB連接是什么原因?

    使用USB3.0 + dp2lane mode),請(qǐng)問可能是什么原因嗎? 有兩個(gè)猜測(cè): 1. 檢查 usb3.0+dp2lane 后是否會(huì)再次枚舉USB模式已協(xié)商。 枚舉失敗 2
    發(fā)表于 02-23 08:17

    枚舉有多大?c語(yǔ)言枚舉end的作用是什么?

    枚舉有多大?c語(yǔ)言枚舉end的作用是什么? 枚舉在C語(yǔ)言中是一種常見的數(shù)據(jù)類型,用于定義一組相互關(guān)聯(lián)的常量或者變量。它通常用于表示一系列可能的取值,使得程序更加易讀和易維護(hù)。在C語(yǔ)言中,枚舉
    的頭像 發(fā)表于 01-19 14:19 ?539次閱讀

    redis的淘汰策略

    的寫入。 Redis的淘汰策略主要有以下幾種: LRU(Least Recently Used,最近最少使用): 這是Redis默認(rèn)的淘汰策略。當(dāng)內(nèi)存空間不足時(shí),Redis會(huì)選擇最近最少使用的數(shù)據(jù)對(duì)象進(jìn)行淘汰。這種策略適用于常用
    的頭像 發(fā)表于 12-04 16:23 ?515次閱讀

    java switch case值能為枚舉值嗎

    Java中的switch語(yǔ)句可以接受枚舉類型的值作為參數(shù)。在Java中,枚舉是一種特殊的數(shù)據(jù)類型,它定義了一個(gè)固定數(shù)量的命名常量。因此,可以將枚舉值作為switch語(yǔ)句的case。 首先,讓我們來看
    的頭像 發(fā)表于 11-30 14:41 ?4917次閱讀

    C語(yǔ)言必備知識(shí)枚舉

    枚舉類型是ANSI C新標(biāo)準(zhǔn)所增加的。如果一個(gè)變量只有幾種可能的值,可以定義為枚舉類型。所謂“枚舉是指將變量的值一一列舉出來,變量的值只限于列舉出來的值的范圍內(nèi)。
    的頭像 發(fā)表于 11-29 16:07 ?718次閱讀

    如何在Go中給GORM模型添加枚舉類型

    GORM是一個(gè)流行的Go ORM,它允許我們定義結(jié)構(gòu)體來表示數(shù)據(jù)庫(kù)表,并提供一個(gè)接口來執(zhí)行CRUD操作。枚舉在編程中是一個(gè)有用的特性,它允許我們定義一個(gè)變量可以取的固定值集。在這篇文章中,我們將探索如何向GORM模型添加枚舉。
    的頭像 發(fā)表于 11-28 15:36 ?1275次閱讀