【java設計模式】(7)---策略模式(案例解析)

雨點的名字發表於2020-05-25

策略模式

一、概念

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、策略模式

2、支付平臺選擇(策略模式)

3、Java設計模式-策略模式



別人罵我胖,我會生氣,因為我心裡承認了我胖。別人說我矮,我就會覺得好笑,因為我心裡知道我不可能矮。這就是我們為什麼會對別人的攻擊生氣。
攻我盾者,乃我內心之矛(8)。

相關文章