一篇搞懂策略模式的實戰應用

1、前言

  在上一篇咱們學習了工廠模式的應用,這一篇咱們會接着學習策略模式在實際項目當中的應用。java

  首先,在講策略模式以前,咱們看一下平常生活中關於大型超市的會員策略:
  商場每每根據不一樣的客戶制定不一樣的商品報價策略,好比針對普通會員用戶打9折扣,針對VIP會員打8折,針對超級會員用戶打5折...web

  如今咱們要作一個報價管理的模塊,簡要點就是要針對不一樣的會員客戶,提供不一樣的折扣報價。算法

  咱們通常的代碼多是這樣子的:spring

package com.MyMineBug.demoRun.strategy;

import java.math.BigDecimal;

public class MemberManagement {

	public BigDecimal calculatePrice(String customType){
        if ("普通會員用戶".equals(customType)) {
            System.out.println("抱歉!普通會員用戶打9折!");
            return new BigDecimal("90");
        }else if ("VIP會員".equals(customType)) {
            System.out.println("恭喜你!VIP會員打8折!");
            return new BigDecimal("80");
        }else if("超級會員用戶".equals(customType)){
            System.out.println("恭喜你!超級會員用戶打5折!");
            return new BigDecimal("50");
        }
        //普通用戶都是原價
        return new BigDecimal("100");
    }

}

複製代碼

通過測試,上面的代碼工做的很好,但是上面的代碼是有問題的。上面存在的問題:把不一樣客戶的報價的算法都放在了同一個方法裏面,使得該方法非常龐大(如今是隻是一個演示,因此看起來還不是很臃腫)。編程

若是後面繼續對其進行優化,多是會將每種會員提取出來,做爲一種單獨的算法,可是這樣會違反咱們的開閉原則多線程

開閉原則:

1.對於擴展是開放的(Open for extension)。這意味着模塊的行爲是能夠擴展的。當應用的需求改變時,咱們能夠對模塊進行擴展,使其具備知足那些改變的新行爲。也就是說,咱們能夠改變模塊的功能。app

2.對於修改是關閉的(Closed for modification)。對模塊行爲進行擴展時,沒必要改動模塊的源代碼或者二進制代碼。 ide

那有沒有什麼辦法使得咱們的報價管理便可擴展、可維護,又能夠方便的響應變化呢?固然有解決方案啦,就是咱們下面要講的策略模式函數

2、初步瞭解策略模式

2.1 定義

  策略模式定義了一系列的算法,並將每個算法封裝起來,使每一個算法能夠相互替代,使算法自己和使用算法的客戶端分割開來,相互獨立。學習

2.2 結構

1.策略接口角色IStrategy:用來約束一系列具體的策略算法,策略上下文角色StrategyContext使用此策略接口來調用具體的策略所實現的算法。

2.具體策略實現角色ConcreteStrategy:具體的策略實現,即具體的算法實現。

3.策略上下文角色StrategyContext:策略上下文,負責和具體的策略實現交互,一般策略上下文對象會持有一個真正的策略實現對象,策略上下文還可讓具體的策略實現從其中獲取相關數據,回調策略上下文對象的方法。

2.3 使用策略模式重寫報價管理模塊

  這裏實現,咱們是經過spring註解的方式,將咱們策略的實現類注入到map裏面,項目啓動時,會自動將IStrategy的實現類注入到策略上下文StrategyContext,具體實現以下:

公共報價策略接口:

package com.MyMineBug.demoRun.strategy;

import java.math.BigDecimal;

public interface IStrategy {

	/** * 計算價格 * @return */
	public BigDecimal calculatePrice();
}

複製代碼

普通會員用戶報價策略實現:

package com.MyMineBug.demoRun.strategy;

import java.math.BigDecimal;

import org.springframework.stereotype.Component;
@Component("GeneralMember")
public class GeneralMember implements IStrategy{

	@Override
	public BigDecimal calculatePrice() {
		return new BigDecimal("90");
	}

}
複製代碼

VIP會員用戶策略實現:

package com.MyMineBug.demoRun.strategy;

import java.math.BigDecimal;

import org.springframework.stereotype.Component;

@Component("VipMember")
public class VipMember implements IStrategy{

	@Override
	public BigDecimal calculatePrice() {
		return new BigDecimal("80");
	}

}
複製代碼

超級會員用戶策略實現:

package com.MyMineBug.demoRun.strategy;

import java.math.BigDecimal;

import org.springframework.stereotype.Component;

@Component("SuperMember")
public class SuperMember implements IStrategy{

	@Override
	public BigDecimal calculatePrice() {
		return new BigDecimal("50");
	}

}
複製代碼

策略報價上下文:

package com.MyMineBug.demoRun.strategy;
/** * 策略管理器 * @author 18360 * */

import java.math.BigDecimal;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

@Component
public class StrategyContext {
	
	private final Map<String, IStrategy> strategyMap = new ConcurrentHashMap<String, IStrategy>();
	
	/** * 注入全部實現了IStrategy接口的Bean * @param strategyMap */
	@Autowired
	public void StrategyInterface(Map<String, IStrategy> strategyMap) {
		this.strategyMap.clear();
		strategyMap.forEach((k, v)-> this.strategyMap.put(k, v));
	}
	
    /** * 計算價格 * @param memberLevel 會員等級 * @return 價格 */
    public BigDecimal calculatePrice(String memberLevel) {
    	if(!StringUtils.isEmpty(memberLevel)){
    		return strategyMap.get(memberLevel).calculatePrice();
    	}
		return null;
    }
}

複製代碼

因爲是spring boot項目,因此咱們能夠在controller層模擬外部不一樣的會員用戶的報價,代碼以下:

package com.MyMineBug.demoRun.controller;

import java.math.BigDecimal;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.MyMineBug.demoRun.strategy.StrategyContext;

@RestController
@RequestMapping
public class StrategyController {

	@Autowired
	private StrategyContext strategyContext;

	@RequestMapping("/calculatePrice")
	public BigDecimal calculatePrice(@RequestParam("memberLevel") String memberLevel) {
		return strategyContext.calculatePrice(memberLevel);
	}
	
	@RequestMapping("/whello")
	public String hello() {
		return "hello run"; 
	}

}

複製代碼

若是啓動spring boot項目報錯,須要在啓動入口加上掃描註解:@ComponentScan(basePackages = {"com.MyMineBug.demoRun"})

啓動服務後,在界面輸入http://localhost:8080/calculatePrice?memberLevel=GeneralMember

輸出:

3、深刻理解策略模式

3.1 策略模式的做用

  就是把具體的算法實現從業務邏輯中剝離出來,成爲一系列獨立算法類,使得它們能夠相互替換。

3.2 策略模式的着重點

  不須要去管如何來實現算法,而是管如何組織和調用這些算法,從而讓咱們的程序結構更加的靈活、可擴展。

  咱們前面的第一個報價管理的示例,發現每一個策略算法實現對應的都是在MemberManagement中calculatePrice方法中的if else語句裏面,咱們知道if else if語句裏面的代碼在執行的可能性方面能夠說是平等的,你要麼執行if,要麼執行else,要麼執行else if。
  策略模式就是把各個平等的具體實現進行抽象、封裝成爲獨立的算法類,而後經過上下文和具體的算法類來進行交互。各個策略算法都是平等的,地位是同樣的,正是因爲各個算法的平等性,因此它們纔是能夠相互替換的。雖然咱們能夠動態的切換各個策略,可是同一時刻只能使用一個策略。

4、策略模式在JDK中的運用

  在多線程編程中,咱們常用線程池來管理線程,以減緩線程頻繁的建立和銷燬帶來的資源的浪費,在建立線程池的時候,常用一個工廠類來建立線程池Executors,實際上Executors的內部使用的是類ThreadPoolExecutor。它有一個最終的構造函數以下:

corePoolSize:線程池中的核心線程數量,即便這些線程沒有任務幹,也不會將其銷燬。

maximumPoolSize:線程池中的最多可以建立的線程數量。

keepAliveTime:當線程池中的線程數量大於corePoolSize時,多餘的線程等待新任務的最長時間。

unit:keepAliveTime的時間單位。

workQueue:在線程池中的線程尚未還得及執行任務以前,保存任務的隊列(當線程池中的線程都有任務在執行的時候,仍然有任務不斷的提交過來,這些任務保存在workQueue隊列中)。

threadFactory:建立線程池中線程的工廠。

handler:當線程池中沒有多餘的線程來執行任務,而且保存任務的多列也滿了(指的是有界隊列),對仍在提交給線程池的任務的處理策略。

RejectedExecutionHandler 是一個策略接口,用在當線程池中沒有多餘的線程來執行任務,而且保存任務的多列也滿了(指的是有界隊列),對仍在提交給線程池的任務的處理策略。

公共策略接口:

這個策略接口有四個實現類:

AbortPolicy:該策略是直接將提交的任務拋棄掉,並拋出RejectedExecutionException異常。

DiscardPolicy:該策略也是將任務拋棄掉(對於提交的任務無論不問,什麼也不作),不過並不拋出異常。

DiscardOldestPolicy:該策略是當執行器未關閉時,從任務隊列workQueue中取出第一個任務,並拋棄這第一個任務,進而有空間存儲剛剛提交的任務。使用該策略要特別當心,由於它會直接拋棄以前的任務。

CallerRunsPolicy:該策略並無拋棄任何的任務,因爲線程池中已經沒有了多餘的線程來分配該任務,該策略是在當前線程(調用者線程)中直接執行該任務。

類ThreadPoolExecutor中持有一個RejectedExecutionHandler接口的引用,以便在構造函數中能夠由外部客戶端本身制定具體的策略並注入。下面看一下其類圖:

5、總結

策略模式的優勢:

  1.策略模式的功能就是經過抽象、封裝來定義一系列的算法,使得這些算法能夠相互替換,因此爲這些算法定義一個公共的接口,以約束這些算法的功能實現。若是這些算法具備公共的功能,能夠將接口變爲抽象類,將公共功能放到抽象父類裏面。

  2.策略模式的一系列算法是能夠相互替換的、是平等的,寫在一塊兒就是if-else組織結構,若是算法實現裏又有條件語句,就構成了多重條件語句,能夠用策略模式,避免這樣的多重條件語句。

  3.擴展性更好:在策略模式中擴展策略實現很是的容易,只要新增一個策略實現類,而後在使用策略實現的地方,使用這個新的策略實現就行了。

策略模式的缺點:

  1.客戶端必須瞭解全部的策略,清楚它們的不一樣: 若是由客戶端來決定使用何種算法,那客戶端必須知道全部的策略,清楚各個策略的功能和不一樣,這樣才能作出正確的選擇,可是這暴露了策略的具體實現。

  2.增長了對象的數量: 因爲策略模式將每一個具體的算法都單獨封裝爲一個策略類,若是可選的策略有不少的話,那對象的數量也會不少。

  3.只適合偏平的算法結構:因爲策略模式的各個策略實現是平等的關係(可相互替換),實際上就構成了一個扁平的算法結構。即一個策略接口下面有多個平等的策略實現(多個策略實現是兄弟關係),而且運行時只能有一個算法被使用。這就限制了算法的使用層級,且不能被嵌套。

  策略模式的本質:分離算法,選擇實現。

  若是以爲還不錯,請點個贊!!!

  Share Technology And Love Life

相關文章
相關標籤/搜索