一、為什么講策略模式
策略模式,應該是工作中比較常用的設計模式,調用方自己選擇用哪一種策略完成對數據的操作,也就是“一個類的行為或其算法可以在運行時更改”
我個人的理解是 將一些除了過程不同其他都一樣的函數封裝成策略,然后調用方自己去選擇想讓數據執行什么過程策略。常見的例子為根據用戶分類推薦不同的排行榜(用戶關注點不一樣,推薦榜單就不一樣)
和單例模式一樣,隨著時間發展,我不再推薦經典策略模式,更推薦簡單策略用枚舉策略模式,復雜地用工廠策略模式。下面引入一個例子,我們的需求是:對一份股票數據列表,給出低價榜、高價榜、漲幅榜。這其中只有排序條件的區別,比較適合作為策略模式的例子
基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
- 項目地址:https://github.com/YunaiV/ruoyi-vue-pro
- 視頻教程:https://doc.iocoder.cn/video/
二、經典策略模式
數據DTO
@Data
publicclassStock{
//股票交易代碼
privateStringcode;
//現價
privateDoubleprice;
//漲幅
privateDoublerise;
}
抽象得到的策略接口
publicinterfaceStrategy{
/**
*將股票列表排序
*
*@paramsource源數據
*@return排序后的榜單
*/
Listsort(Listsource) ;
}
實現我們的策略類
/**
*高價榜
*/
publicclassHighPriceRankimplementsStrategy{
@Override
publicListsort(Listsource) {
returnsource.stream()
.sorted(Comparator.comparing(Stock::getPrice).reversed())
.collect(Collectors.toList());
}
}
/**
*低價榜
*/
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());
}
}
經典的Context類,
publicclassContext{
privateStrategystrategy;
publicvoidsetStrategy(Strategystrategy){
this.strategy=strategy;
}
publicListgetRank(Listsource) {
returnstrategy.sort(source);
}
}
于是 我們順禮成章地得到調用類--榜單實例RankServiceImpl
@Service
publicclassRankServiceImpl{
/**
*dataService.getSource()提供原始的股票數據
*/
@Resource
privateDataServicedataService;
/**
*前端傳入榜單類型,返回排序完的榜單
*
*@paramrankType榜單類型
*@return榜單數據
*/
publicListgetRank(StringrankType) {
//創建上下文
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");
}
//然后執行策略
returncontext.getRank(dataService.getSource());
}
}
我們可以看到經典方法,創建了一個接口、三個策略類,還是比較啰嗦的。調用類的實現也待商榷,新增一個策略類還要修改榜單實例(可以用抽象工廠解決,但是復雜度又上升了)。加之我們有更好的選擇,所以此處不再推薦經典策略模式
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
三、基于枚舉的策略模式
這里對這種簡單的策略,推薦用枚舉進行優化。枚舉的本質是創建了一些靜態類的集合。
我下面直接給出例子,大家可以直觀感受一下
枚舉策略類
publicenumRankEnum{
//以下三個為策略實例
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) ;
}
對應的調用類也得以優化,榜單實例RankServiceImpl
@Service
publicclassRankServiceImpl{
/**
*dataService.getSource()提供原始的股票數據
*/
@Resource
privateDataServicedataService;
/**
*前端傳入榜單類型,返回排序完的榜單
*
*@paramrankType榜單類型形似RankEnum.HighPrice.name()
*@return榜單數據
*/
publicListgetRank(StringrankType) {
//獲取策略,這里如果未匹配會拋IllegalArgumentException異常
RankEnumrank=RankEnum.valueOf(rankType);
//然后執行策略
returnrank.sort(dataService.getSource());
}
}
可以看到,如果策略簡單的話,基于枚舉的策略模式優雅許多,調用方也做到了0修改,但正確地使用枚舉策略模式需要額外考慮以下幾點。
- 枚舉的策略類是公用且靜態,這意味著這個策略過程不能引入非靜態的部分,擴展性受限
- 策略模式的目標之一,是優秀的擴展性和可維護性,最好能新增或修改某一策略類時,對其他類是無改動的。而枚舉策略如果過多或者過程復雜,維護是比較困難的,可維護性受限
四、基于工廠的策略模式
為了解決良好的擴展性和可維護性,我更推薦以下利用spring自帶beanFactory的優勢,實現一個基于工廠的策略模式。
策略類改動只是添加了@Service注解,并指定了Service的value屬性
/**
*高價榜
*注意申明Service.value=HighPrice,他是我們的key,下同
*/
@Service("HighPrice")
publicclassHighPriceRankimplementsStrategy{
@Override
publicListsort(Listsource) {
returnsource.stream()
.sorted(Comparator.comparing(Stock::getPrice).reversed())
.collect(Collectors.toList());
}
}
/**
*低價榜
*/
@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());
}
}
調用類修改較大,接入借助spring工廠特性,完成策略類
@Service
publicclassRankServiceImpl{
/**
*dataService.getSource()提供原始的股票數據
*/
@Resource
privateDataServicedataService;
/**
*利用注解@Resource和@Autowired特性,直接獲取所有策略類
*key=@Service的value
*/
@Resource
privateMaprankMap;
/**
*前端傳入榜單類型,返回排序完的榜單
*
*@paramrankType榜單類型和Service注解的value屬性一致
*@return榜單數據
*/
publicListgetRank(StringrankType) {
//判斷策略是否存在
if(!rankMap.containsKey(rankType)){
thrownewIllegalArgumentException("rankTypenotfound");
}
//獲得策略實例
Strategyrank=rankMap.get(rankType);
//執行策略
returnrank.sort(dataService.getSource());
}
}
若讀者使用的不是Spring,也可以找找對應框架的工廠模式實現,或者自己實現一個抽象工廠。
工廠策略模式會比枚舉策略模式啰嗦,但也更加靈活、易擴展性和易維護。故簡單策略推薦枚舉策略模式,復雜策略才推薦工廠策略模式。
審核編輯 :李倩
-
框架
+關注
關注
0文章
403瀏覽量
17543 -
函數
+關注
關注
3文章
4346瀏覽量
62974
原文標題:為什么我不再推薦枚舉策略模式?
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
設計模式-策略模式
![設計<b class='flag-5'>模式</b>-<b class='flag-5'>策略</b><b class='flag-5'>模式</b>](http://m.xsypw.cn/images/chaijie_default.png)
評論