點外賣,讓我想起了 策略模式
今天給大家分享的是策略模式,具體內(nèi)容大綱如下:
生活案例
在這互聯(lián)網(wǎng)時代,尤其是在城市中,有一幫騎著電瓶車,穿梭在大街小巷中,這幫人就是外賣小哥。
對于點外賣,我也點過不少。有一次,外賣下單的時候,我突然聯(lián)想到了一個設(shè)計模式---策略模式。
策略模式是個啥?
策略模式:英文為Strategy Pattern,是指定義了算法家族、分別封裝起來,讓他們之間可以相互替換,此設(shè)計模式讓算法的變化不會影響到使用算法的用戶。
英文
Define a family of algorithms,encapsulate each one,and make them interchangeable.
大致意思:定義一組算法,將每個算法都封裝起來,并且使它們之間可以互換。
在策略模式中,一個類的行為或其算法可以在運行時更改。這種類型的設(shè)計模式屬于行為型模式。
策略模式通用代碼
java代碼實現(xiàn)如下:
- class Client {
- //抽象策略類 Strategy
- interface IStrategy {
- void algorithm();
- }
- //具體策略類 ConcreteStrategy
- static class ConcreteStrategyA implements IStrategy {
- @Override
- public void algorithm() {
- System.out.println("Strategy A");
- }
- }
- //具體策略類 ConcreteStrategy
- static class ConcreteStrategyB implements IStrategy {
- @Override
- public void algorithm() {
- System.out.println("Strategy B");
- }
- }
- //上下文環(huán)境
- static class Context {
- private IStrategy mStrategy;
- public Context(IStrategy strategy) {
- this.mStrategy = strategy;
- }
- public void algorithm() {
- this.mStrategy.algorithm();
- }
- }
- public static void main(String[] args) {
- //選擇一個具體策略
- IStrategy strategy = new ConcreteStrategyA();
- //來一個上下文環(huán)境
- Context context = new Context(strategy);
- //客戶端直接讓上下文環(huán)境執(zhí)行算法
- context.algorithm();
- }
- }
從上面的通用代碼,我們可以得知其UML圖。
策略模式UML圖
策略模式中的角色
從 UML 類圖中,我們可以看到,策略模式主要包含三種角色:
- 上下文角色(Context):用來操作策略的上下文環(huán)境,屏蔽高層模塊(客戶端)對策略,算法的直接訪問,封裝可能存在的變化;
- 抽象策略角色(Strategy):規(guī)定策略或算法的行為;
- 具體策略角色(ConcreteStrategy):具體的策略或算法實現(xiàn);
策略模式優(yōu)缺點
優(yōu)點
- 策略模式符合開閉原則
- 避免使用多重轉(zhuǎn)換語句,比如:if...else、switch語句。
- 使用策略模式可以提高算法的保密性和安全性
缺點
- 客戶端必須知道所有策略,并且自行決定使用哪一種策略
- 代碼中產(chǎn)生非常多的策略類,增加后期維護難度
策略模式使用場景
在日常開發(fā)中,策略模式適用于以下三種場景:
- 針對同一類型問題,有多重處理方式,每一種都能獨立解決問題。
- 算法需要自由切換的場景。
- 需要屏蔽算法規(guī)則的場景
這個說起來,還是不太好理解。
下面,我們就來使用生活案例來實現(xiàn),讓大家知道策略模式到底是怎么使用的。
支付案例代碼重構(gòu),三個版本
外面下單,選擇支付方式的時候,我覺這個功能,我們可以模仿著使用策略模式來實現(xiàn)一下。下面我們通過三個版本的迭代來實現(xiàn),很有意思的。
第一版
先定義一個抽象類Pay:
- //定義抽象類,我們可以把一些共用功能放在抽象類里實現(xiàn)
- //比如:可用余額和本次支付金額進行比較,統(tǒng)一返回“支付失敗”
- public abstract class Pay {
- abstract void doPay();
- }
下面模擬三種支付方式:
- public class AliPay extends Pay {
- @Override
- public void doPay() {
- System.out.println("使用支付寶支付");
- }
- }
- public class UnionPay extends Pay {
- @Override
- public void doPay() {
- System.out.println("使用銀聯(lián)支付");
- }
- }
- public class WechatPay extends Pay {
- @Override
- public void doPay() {
- System.out.println("使用微信支付");
- }
- }
我們再來進行支付:
- public class PayTest {
- public static void main(String[] args) {
- //把選擇權(quán)交給了用戶
- Order order = new Order(new WechatPay());
- order.pay();*
- }
- }
運行結(jié)果:
- 使用微信支付
這樣我們就使用策略模式實現(xiàn)了簡單版本的支付,但是其中有個很不爽的地方,就是每次還得自己手工new一個支付方式的對象。鑒于此,我們對第一版進行重構(gòu)。
第二版
前面的實現(xiàn)都不變,變化的是發(fā)起支付的時候,只要前端傳一個key過來就可以了,實現(xiàn)如下:
- public class PayTest {
- public static void main(String[] args) {
- String payKey = "Wechat";
- Order order = null;
- //通過判斷前端傳過來的key,判斷使用哪種支付方式
- if (payKey.equals("Ali")) {
- order = new Order(new AliPay());
- } else if (payKey.equals("Wechat")) {
- order = new Order(new WechatPay());
- } else if (payKey.equals("union")) {
- order = new Order(new UnionPay());
- }else {
- //給出一個默認方式
- order = new Order(new WechatPay());
- }
- order.pay();
- }
- }
運行結(jié)果
- 使用微信支付
這樣我們就實現(xiàn)了,通過前端傳過來的key,然后選出對應(yīng)的支付方式。但是問題又來了,如果支付方式不斷增多,那這里的if...else豈不是會越來越多嗎?后續(xù)維護成本不是越來越大嗎?
于是,第三版就有了。
第三版
在第二版中,會出現(xiàn)大量的if...else,會給后續(xù)的代碼維護帶來不便,于是在這一版中,我們對其進行重構(gòu),引入了注冊式單例模式。
- import java.util.HashMap;
- import java.util.Map;
- public enum PayStrategyEnum {
- ALI_PAY("Ali"),
- WECHAT_PAY("Wechat"),
- UNION_PAY("union"),
- //默認使用微信支付
- DEFAULT_PAY("Wechat");
- private String key;
- PayStrategyEnum(String key) {
- this.key = key;
- }
- private static final Map<String, Pay> payKeyMap = new HashMap();
- static {
- payKeyMap.put(ALI_PAY.key, new AliPay());
- payKeyMap.put(WECHAT_PAY.key, new WechatPay());
- payKeyMap.put(UNION_PAY.key, new UnionPay());
- payKeyMap.put(DEFAULT_PAY.key, new WechatPay());
- }
- public static Pay getPay(String payKey) {
- if (!payKeyMap.containsKey(payKey)) {
- return payKeyMap.get(DEFAULT_PAY.key);
- }
- return payKeyMap.get(payKey);
- }
- }
然后,在訂單支付的時候就變成了這樣了:
- public class PayTest {
- public static void main(String[] args) {
- String payKey = "Wechat";
- Order order = new Order(PayStrategyEnum.getPay(payKey));
- order.pay();
- }
- }
運行結(jié)果
- 使用微信支付
這樣,我們就成功的規(guī)避了大量的if...else了,爽歪歪!
其實,上面三個版本的代碼,是不是覺得很爽,這就是設(shè)計模式的強大之處。
PS:關(guān)于上面的三個版本,其實我們還可以繼續(xù)完善,繼續(xù)重構(gòu),感興趣的你可以去試試如何繼續(xù)重構(gòu)。
總結(jié)
好了,今天的策略模式就到這里。其實,設(shè)計模式在大多數(shù)情況下,是不會單獨存在的,都是使用多種設(shè)計模式混合起來使用的。
策略模式使用的就是面向?qū)ο蟮睦^承和多態(tài)機制,從而實現(xiàn)同一行為在不同場景下不同實現(xiàn)。
最好記的案例:
我們可以使用不同的交通工具去北京玩
坐飛機、坐高鐵、坐汽車、開車、騎車。方式很多,你想選哪一條就選那一條。
最后用一句話來總結(jié)策略模式:
條條大路通羅馬
本文轉(zhuǎn)載自微信公眾號「Java后端技術(shù)全棧」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系Java后端技術(shù)全棧公眾號。






























