策略模式
一、概念
1、理解策略模式
策略模式是一種行為型模式,它將物件和行為分開,將行為定義為 一個行為介面
和 具體行為的實現
。策略模式最大的特點是行為的變化,行為之間可以相互替換。
每個if判斷都可以理解為就是一個策略。
2、策略模式特點
策略模式把物件本身和行為區分開來,因此我們整個模式也分為三個部分。
1、抽象策略類(Strategy):策略的抽象,行為方式的抽象
2、具體策略類(ConcreteStrategy):具體的策略實現,每一種行為方式的具體實現。
3、環境類(Context):用來封裝具體行為,操作策略的上下文環境。
3、舉例理解(叫車)
這裡舉個簡單的例子,來理解開發中運用策略模式的場景。
有一個叫車軟體,現在有三種計費模式給 使用者 選擇,1、拼車 2、快車 3、豪車這個時候使用者就是物件,三種計費方式就是行為,可以根據不同的行為計算不同不通的值。
1)傳統實現方式
程式碼
/**
* @Description: 這裡只展示計費最終費用示例
*
* @param type 計費型別
* @param originalPrice 原始價格
*/
public Double calculationPrice(String type, Double originalPrice) {
//拼車計費
if (type.equals("pc")) {
return originalPrice * 0.5;
}
//快車計費
if (type.equals("kc")) {
return originalPrice * 1;
}
//豪車計費
if (type.equals("hc")) {
return originalPrice * 2;
}
return originalPrice;
}
傳統的實現方式,通過傳統if程式碼判斷。這樣就會導致後期的維護性非常差。當後期需要新增計費方式,還需要在這裡再加上if(),也不符合設計模式的開閉原則。
2)策略模式實現
抽象策略類
/**
* 出行策略介面
*/
public interface PriceStrategy {
/**
* @param originalPrice 原始價格
* @return 計算後的價格
*/
Double countPrice(Double originalPrice);
}
具體策略實現類
/**
* @Description: 拼車的計費方式
*/
public class PcStrategy implements PriceStrategy {
@Override
public Double countPrice(Double originalPrice) {
return originalPrice * 0.5;
}
}
/**
* @Description: 快車的計費方式
*/
public class KcStrategy implements PriceStrategy {
@Override
public Double countPrice(Double originalPrice) {
return originalPrice * 1;
}
}
/**
* @Description: 拼車的計費方式
*/
public class HcStrategy implements PriceStrategy {
@Override
public Double countPrice(Double originalPrice) {
return originalPrice * 2;
}
}
環境類
也叫做上下文類或環境類,起承上啟下封裝作用。
/**
* 負責和具體的策略類互動
* 這樣的話,具體的演算法和直接的客戶端呼叫分離了,使得演算法可以獨立於客戶端獨立的變化。
* 如果使用spring的依賴注入功能,還可以通過配置檔案,動態的注入不同策略物件,動態的切換不同的演算法.
*/
public class PriceContext {
/**
* 出行策略介面
*/
private PriceStrategy riceStrategy;
/**
* 建構函式注入
*/
public PriceContext(PriceStrategy riceStrategy) {
this.riceStrategy = riceStrategy;
}
/**
* 計算價格
*/
public Double countPrice(Double originalPrice) {
return riceStrategy.countPrice(originalPrice);
}
}
測試類
public static void main(String[] args) {
//具體行為策略
PriceStrategy pcStrategy = new PcStrategy();
PriceStrategy kcStrategy = new KcStrategy();
PriceStrategy hcStrategy = new HcStrategy();
//使用者選擇不同的策略
PriceContext pcContext = new PriceContext(pcStrategy);
PriceContext kcContext = new PriceContext(kcStrategy);
PriceContext hcContext = new PriceContext(hcStrategy);
System.out.println("拼車價格 = " + pcContext.countPrice(10D));
System.out.println("快車價格 = " + kcContext.countPrice(10D));
System.out.println("豪車價格 = " + hcContext.countPrice(10D));
}
執行結果
拼車價格 = 5.0
快車價格 = 10.0
豪車價格 = 20.0
整理流程就是這個樣的,這裡有一點需要注意 我在做測試的時候具體策略物件都是new出來,而實際運用應該通過spring來管理,這樣才能做到真正的開閉原則。下面會在舉例。
4、策略模式優缺點
優點
1)避免使用多重條件判斷
如果沒有策略模式,一個策略家族有多個策略演算法,一會要使用A策略,一會要使用B策略,怎麼設計呢?使用多重if的條件語句?多重條件語句不易維護,而且出錯的概率大大增強。
使用策略模式後,簡化了操作,同時避免了條件語句判斷。
2)擴充套件性良好
在現有的系統中增加一個策略太容易了,只要實現介面就可以了,其他都不用修改,類似於一個可反覆拆卸的外掛,這大大地符合了OCP原則。
缺點
1)策略類數量增多
策略模式一個明顯的缺點是當備用行為過多時,行為物件會非常龐大
5、策略模式運用場景
通過上面的優缺點我們可以很好的去思考,什麼場景下可以考慮用策略模式?
我的理解就是:每個if判斷都可以理解為就是一個策略。按理說都可以採用策略模式,但是如果if else裡面的邏輯不多,且複用性很低,那就不需要。如果if裡面的行為比較大
而且這些行為複用性比較高就可以考慮通過採用策略模式。
在我們生活中比較常見的應用模式有:
1、電商網站支付方式,一般分為銀聯、微信、支付寶,可以採用策略模式
2、電商網站活動方式,一般分為滿減送、限時折扣、包郵活動,拼團等可以採用策略模式
二、策略模式實戰示例
最近正在做到電商專案,因為有多個活動,所以我就考慮用策略模式來實現。我們活動分為很多種滿減送,包郵活動,限時折扣等等,這裡大致寫下對於多個活動如何去使用策略模式。
1、Order實體
活動是跟訂單繫結在一起的,只有下了單才去計算這個訂單走了哪個活動。
/**
* @Description: 訂單實體
*/
public class Order {
/**
* 使用者ID
*/
private Long userId;
/**
* 訂單編號
*/
private String orderNumber;
/**
* 購買數量
*/
private Integer goodsNumber;
/**
* 訂單運費
*/
private Double orderFreight;
/**
* 訂單總價(訂單價格 + 運費)
*/
private Double orderPrice;
/**
* 活動型別 1、包郵 2、滿減送 3、限時折扣
*/
private String activityType;
/**
* 活動ID
*/
private String activityId;
//省略get set方法
2、ActivityStrategy(活動策略介面)
/**
* 定義一個總的活動抽象
*/
public interface ActivityStrategy {
/**
* 定義一個我們優惠活動的價格演算法方法
*/
Order calculate (Order order);
}
3、具體活動策略實現類
FreeShippingActivity
包郵
/**
* @Description: 包郵活動
*/
@Component
@Service("freeShippingActivity")
public class FreeShippingActivity implements ActivityStrategy {
@Override
public Order calculate(Order order) {
//包郵活動是一個大的主題 ,裡面可以建立很多小活動 比如價格滿100包郵活動,或者滿2件以上包郵活動,江浙滬包郵活動等等
//如果這裡通過活動ID獲取使用者具體選擇了哪一個活動。
String activityId = order.getActivityId();
//查詢資料庫
System.out.println("模擬查詢資料庫 ,當前的活動是滿100包郵");
order.setOrderFreight(0.0D);
return order;
}
}
FullDeliveryActivity
(滿減送活動)
/**
* @Description: 滿減送活動
*/
@Component
@Service("fullDeliveryActivity")
public class FullDeliveryActivity implements ActivityStrategy {
@Override
public Order calculate(Order order) {
//如果這裡通過活動ID獲取使用者具體選擇了哪一個活動。
String activityId = order.getActivityId();
//查詢資料庫
System.out.println("模擬查詢資料庫 ,當前的活動是滿100減20的");
if (order.getOrderPrice() > 100) {
order.setOrderPrice(order.getOrderPrice() - 20);
}
return order;
}
}
LimitDiscountActivity
(限時折扣活動)
/**
* @Description: 限時折扣活動
*/
@Component
@Service("limitDiscountActivity")
public class LimitDiscountActivity implements ActivityStrategy {
@Override
public Order calculate(Order order) {
//如果這裡通過活動ID獲取使用者具體選擇了哪一個活動。
String activityId = order.getActivityId();
//查詢資料庫
System.out.println("模擬查詢資料庫 ,當前的活動是截至2020.10.1前 打9折");
order.setOrderPrice(order.getOrderPrice() * 0.9);
return order;
}
}
3、環境類
/**
* 負責和具體的策略類互動 動態的切換不同的演算法
*/
@Component
public class ActivityContext {
@Autowired
private FreeShippingActivity freeShippingActivity;
@Autowired
FullDeliveryActivity fullDeliveryActivity;
@Autowired
LimitDiscountActivity limitDiscountActivity;
/**
* 出行策略介面
*/
private final Map<String, ActivityStrategy> activityStrategyMap = new HashMap();
/**
* 初始化 把這幾個活動的示例 初始化的時候就裝到一個map集合中
*/
@PostConstruct
public void init() {
//1、包郵 2、滿減送 3、限時折扣
activityStrategyMap.put("1", freeShippingActivity);
activityStrategyMap.put("2", fullDeliveryActivity);
activityStrategyMap.put("3", limitDiscountActivity);
}
/**
* 計算價格
*/
public Order calculate(Order order) {
ActivityStrategy activityStrategy = activityStrategyMap.get(order.getActivityType());
return activityStrategy.calculate(order);
}
}
4、測試類
@RestController
public class PayController {
@Autowired
private ActivityContext activityContext;
@RequestMapping("/test")
public Object test(){
Order order = new Order();
//1 代表包郵
order.setActivityType("1");
//具體活動ID
order.setActivityId("12");
//總價
order.setOrderPrice(200D);
//運費
order.setOrderFreight(10D);
return activityContext.calculate(order);
}
}
//輸出: 模擬查詢資料庫 ,當前的活動是包郵
總結
這裡我們沒有用到if else來判斷使用者到底選擇了哪個活動型別,而是通過先把所有活動的bean實體裝入一個map中,然後通過activityType 來獲取具體是哪個活動型別。
以後新新增一個活動,比如拼團活動,我們只需做兩步
1、新建一個拼團活動策略類 實現總策略介面 2、activityStrategyMap中put這個折扣活動的bean實體。
除了map管理bean外,還有一種方式就是可以通過spring來管理這些具體的策略類
//freeShippingActivity 這個值可以在資料庫新增一張表來管理,然後來讀取 這樣的話新建活動只需要做上面的第一步,程式碼更加好維護了。
ActivityStrategy payStrategy= SpringUtils.getBean("freeShippingActivity", ActivityStrategy.class);
這裡只是列了個架構,實際開發中比這個複雜多了,因為可以同時選擇多個活動,活動於活動之間又會有互斥關係。
參考
1、策略模式
別人罵我胖,我會生氣,因為我心裡承認了我胖。別人說我矮,我就會覺得好笑,因為我心裡知道我不可能矮。這就是我們為什麼會對別人的攻擊生氣。
攻我盾者,乃我內心之矛(8)。