設計模式-3.策略模式

在講策略模式之前,我們先看一個日常生活中的小例子:

  現實生活中我們到商場買東西的時候,賣場往往根據不同的客戶制定不同的報價策略,比如針對新客戶不打折扣,針對老客戶打9折,針對VIP客戶打8折...

  現在我們要做一個報價管理的模塊,簡要點就是要針對不同的客戶,提供不同的折扣報價。

如果是有你來做,你會怎麼做?

我們很有可能寫出下面的代碼:

package strategy.examp02;
 
import java.math.BigDecimal;
 
public class QuoteManager {
 
    public BigDecimal quote(BigDecimal originalPrice,String customType){
        if ("新客戶".equals(customType)) {
            System.out.println("抱歉!新客戶沒有折扣!");
            return originalPrice;
        }else if ("老客戶".equals(customType)) {
            System.out.println("恭喜你!老客戶打9折!");
            originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP);
            return originalPrice;
        }else if("VIP客戶".equals(customType)){
            System.out.println("恭喜你!VIP客戶打8折!");
            originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP);
            return originalPrice;
        }
        //其他人員都是原價
        return originalPrice;
    }
 
}

經過測試,上面的代碼工作的很好,可是上面的代碼是有問題的。上面存在的問題:把不同客戶的報價的算法都放在了同一個方法裏面,使得該方法很是龐大(現在是隻是一個演示,所以看起來還不是很臃腫)。

下面看一下上面的改進,我們把不同客戶的報價的算法都單獨作爲一個方法
 

package strategy.examp02;
 
import java.math.BigDecimal;
 
public class QuoteManagerImprove {
 
    public BigDecimal quote(BigDecimal originalPrice, String customType){
        if ("新客戶".equals(customType)) {
            return this.quoteNewCustomer(originalPrice);
        }else if ("老客戶".equals(customType)) {
            return this.quoteOldCustomer(originalPrice);
        }else if("VIP客戶".equals(customType)){
            return this.quoteVIPCustomer(originalPrice);
        }
        //其他人員都是原價
        return originalPrice;
    }
 
    /**
     * 對VIP客戶的報價算法
     * @param originalPrice 原價
     * @return 折後價
     */
    private BigDecimal quoteVIPCustomer(BigDecimal originalPrice) {
        System.out.println("恭喜!VIP客戶打8折");
        originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP);
        return originalPrice;
    }
 
    /**
     * 對老客戶的報價算法
     * @param originalPrice 原價
     * @return 折後價
     */
    private BigDecimal quoteOldCustomer(BigDecimal originalPrice) {
        System.out.println("恭喜!老客戶打9折");
        originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP);
        return originalPrice;
    }
 
    /**
     * 對新客戶的報價算法
     * @param originalPrice 原價
     * @return 折後價
     */
    private BigDecimal quoteNewCustomer(BigDecimal originalPrice) {
        System.out.println("抱歉!新客戶沒有折扣!");
        return originalPrice;
    }
 
}

上面的代碼比剛開始的時候要好一點,它把每個具體的算法都單獨抽出來作爲一個方法,當某一個具體的算法有了變動的時候,只需要修改響應的報價算法就可以了。

但是改進後的代碼還是有問題的,那有什麼問題呢?

1.當我們新增一個客戶類型的時候,首先要添加一個該種客戶類型的報價算法方法,然後再quote方法中再加一個else if的分支,是不是感覺很是麻煩呢?而且這也違反了設計原則之一的開閉原則(open-closed-principle).

開閉原則:

  對於擴展是開放的(Open for extension)。這意味着模塊的行爲是可以擴展的。當應用的需求改變時,我們可以對模塊進行擴展,使其具有滿足那些改變的新行爲。也就是說,我們可以改變模塊的功能。

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

2.我們經常會面臨這樣的情況,不同的時期使用不同的報價規則,比如在各個節假日舉行的各種促銷活動時、商場店慶時往往都有普遍的折扣,但是促銷時間一旦過去,報價就要回到正常價格上來。按照上面的代碼我們就得修改if else裏面的代  碼很是麻煩

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

 

定義:

  策略模式定義了一系列的算法,並將每一個算法封裝起來,使每個算法可以相互替代,使算法本身和使用算法的客戶端分割開來,相互獨立。

結構:

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

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

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

UML類圖:

UML序列圖: 

 

策略模式代碼的一般通用實現:

策略接口

package strategy.examp01;
 
//策略接口
public interface IStrategy {
    //定義的抽象算法方法 來約束具體的算法實現方法
    public void algorithmMethod();
}

 具體的策略實現:

package strategy.examp01;
 
// 具體的策略實現
public class ConcreteStrategy implements IStrategy {
    //具體的算法實現
    @Override
    public void algorithmMethod() {
        System.out.println("this is ConcreteStrategy method...");
    }
}
package strategy.examp01;
 
 // 具體的策略實現2
public class ConcreteStrategy2 implements IStrategy {
     //具體的算法實現
    @Override
    public void algorithmMethod() {
        System.out.println("this is ConcreteStrategy2 method...");
    }
}

策略上下文:

package strategy.examp01;
 
/**
 * 策略上下文
 */
public class StrategyContext {
    //持有一個策略實現的引用
    private IStrategy strategy;
    //使用構造器注入具體的策略類
    public StrategyContext(IStrategy strategy) {
        this.strategy = strategy;
    }
 
    public void contextMethod(){
        //調用策略實現的方法
        strategy.algorithmMethod();
    }
}

 

package strategy.examp01;
 
//外部客戶端
public class Client {
    public static void main(String[] args) {
        //1.創建具體測策略實現
        IStrategy strategy = new ConcreteStrategy2();
        //2.在創建策略上下文的同時,將具體的策略實現對象注入到策略上下文當中
        StrategyContext ctx = new StrategyContext(strategy);
        //3.調用上下文對象的方法來完成對具體策略實現的回調
        ctx.contextMethod();
    }
}

策略模式的優點:

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

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

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

 

策略模式的缺點:

    1.客戶端必須瞭解所有的策略,清楚它們的不同:

     如果由客戶端來決定使用何種算法,那客戶端必須知道所有的策略,清楚各個策略的功能和不同,這樣才能做出正確的選擇,但是這暴露了策略的具體實現。

  2.增加了對象的數量:

    由於策略模式將每個具體的算法都單獨封裝爲一個策略類,如果可選的策略有很多的話,那對象的數量也會很多。

  3.只適合偏平的算法結構:

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

 

策略模式的本質:

  分離算法,選擇實現。

  如果你仔細思考策略模式的結構和功能的話,就會發現:如果沒有上下文,策略模式就回到了最基本的接口和實現了,只要是面向接口編程,就能夠享受到面向接口編程帶來的好處,通過一個統一的策略接口來封裝和分離各個具體的策略實現,無需關係具體的策略實現。

  貌似沒有上下文什麼事,但是如果沒有上下文的話,客戶端就必須直接和具體的策略實現進行交互了,尤其是需要提供一些公共功能或者是存儲一些狀態的時候,會大大增加客戶端使用的難度;引入上下文之後,這部分工作可以由上下文來完成,客戶端只需要和上下文進行交互就可以了。這樣可以讓策略模式更具有整體性,客戶端也更加的簡單。

  策略模式體現了開閉原則:策略模式把一系列的可變算法進行封裝,從而定義了良好的程序結構,在出現新的算法的時候,可以很容易的將新的算法實現加入到已有的系統中,而已有的實現不需要修改。

  策略模式體現了里氏替換原則:策略模式是一個扁平的結構,各個策略實現都是兄弟關係,實現了同一個接口或者繼承了同一個抽象類。這樣只要使用策略的客戶端保持面向抽象編程,就可以動態的切換不同的策略實現以進行替換。

 

二.很多時候我們通過spring去管理具體的策略對象。

 

策略接口:

public interface PayStrategy {

	public String payType(String type);
}

具體策略實現1

@Component
public class AliPayStrategy implements PayStrategy{

	@Override
	public String payType(String type) {
		// TODO Auto-generated method stub
		return "支付寶付款";
	}

}

具體策略實現2

@Component
public class WeixinPayStrategy implements PayStrategy	{

	@Override
	public String payType(String type) {
		// TODO Auto-generated method stub
		return "微信付款";
	}

}

我們將策略實現對象的beanId保存到數據庫中,spring的上下文對象可以通過beanId獲取具體的策略實現對象,將策略實現對象交給spring管理,這裏也可以通過工廠的方式去創建具體的策略實現對象

spring的工具類,通過beanid獲取spring管理的對象

@Component
public class SpringUtils implements ApplicationContextAware{
    private static ApplicationContext applicationContext;
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		
		this.applicationContext = applicationContext;
		
	}

	public static ApplicationContext getApplicationContext() {
		
		return applicationContext;
	}
	//通過name獲取bean
	public static Object getBean(String name) {
		
		return applicationContext.getBean(name);
	}
	//通過class 獲取Bean
	public static <T> T getBean(Class<T> clazz) {
		
		return applicationContext.getBean(clazz);
	} 
	//通過name class 獲取bean
	public static <T> T getBean(String name, Class<T> clazz) {
		return applicationContext.getBean(name,clazz);
	}
	
}

具體的實現策略

@Component
public class PayContextStrategy {

	@Autowired
	PayChannelRepository payChannelRepository;
	
//	public String payType(String type) {
//	if(StringUtils.isEmpty(type)) {
//		return "type is not null";
//		
//	}	
//	PayStrategy startegy = PayStrategyFactory.getPayStrategy(type);
//	if(startegy == null) {
//		return "no pay startegy";
//	}
//	return startegy.payType(type);
//	}
	
	    public String payType(String type) {
	    Optional<PayChannel> optional = payChannelRepository.findById(type);
	    PayChannel payChannel = optional.isPresent()?optional.get() : null;
	    if(payChannel==null) {
	    	return null;
	    }
	    String beanId = payChannel.getStrategyBeanId();	
	    PayStrategy startegy = (PayStrategy) SpringUtils.getBean(beanId);
	    if(startegy==null) {
	    	return null;
	    }
		return startegy.payType(type);
		}
}

執行策略代碼,通過type選擇不同的策略執行。

@RestController
public class PayController {

	@Autowired
	PayContextStrategy payContextStrategy;
	
	@RequestMapping("/payType")
	public String payType(String type) {
		return payContextStrategy.payType(type);
     }
	
	
	
 }

思考:看到上面的代碼是不是感覺很熟悉,統一的方法接口,對方法接口的不同實現,通過實例化不同實現類去調用接口,和工廠模式及其相似。具體還是有區別的,策略模式是一種平均選擇算法,爲了減少if else的使用,工廠模式是爲了去創建用戶所需要的對象,策略模式和工廠模式可以互相實現,但是策略模式比工廠模式更爲複雜一些。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章