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