策略模式(Strategy):定義了一系列算法家族,將每種算法分別封裝起來,使得各種算法之間可以互相替換。策略模式可以讓算法的變化不影響使用算法的客戶,符合開放-封閉原則(OCP,Open Closed Principle)。
策略模式舉例比較多的就是商場打折優惠(原價、打八折、滿99減50等)和用戶折扣策略(普通用戶、白金用戶、鑽石王老五等)。我們就以商場打折優惠來看一下策略模式的使用:
不考慮代碼設計的情況,一般會寫出如下代碼:
package com.dcc.openTalk.designPattern.strategyPattern.noDesignPattern;
/**
* 場景:商場打折優惠
* 原價、打八折、滿99減50
* 普通實現如下
*
* @author dxc
* @date 2019/3/30
*/
public class MarketDiscount {
/**
* 計算實付金額
*
* @param totalPrice 商品總額
* @param discountType 打折類型
* @return
*/
public static double calculateActualAmount(double totalPrice, int discountType) {
switch (discountType){
//原價
case 0:
break;
//打八折
case 1:
totalPrice = totalPrice * 0.8;
break;
//滿99減50
case 2:
if (totalPrice >= 99) {
totalPrice = totalPrice - 50;
}
break;
default:
}
return totalPrice;
}
public static void main(String []args){
System.out.println(calculateActualAmount(2019.00, 0));
System.out.println(calculateActualAmount(2019.00, 1));
System.out.println(calculateActualAmount(2019.00, 2));
}
}
當新增加了一種優惠方式,比如半價銷售時,可能需要增加case 分支,這就違反了開放封閉原則。
改用策略模式設計之後是這樣的:
優惠策略接口:
package com.dcc.openTalk.designPattern.strategyPattern.useDesignPattern;
/**
* @author dxc
* @date 2019/3/31
*/
public interface DiscountStrategy {
/**
* 計算實付金額
*
* @param totalPrice 商品總額
* @return
*/
double calculateActualAmount(double totalPrice);
}
優惠策略A對應原價出售:
package com.dcc.openTalk.designPattern.strategyPattern.useDesignPattern;
/**
* 原價處理
*
* @author dxc
* @date 2019/3/31
*/
public class DiscountStrategyA implements DiscountStrategy {
public double calculateActualAmount(double totalPrice) {
return totalPrice;
}
}
優惠策略B對應打八折出售:
package com.dcc.openTalk.designPattern.strategyPattern.useDesignPattern;
/**
* 打八折優惠策略
*
* @author dxc
* @date 2019/3/31
*/
public class DiscountStrategyB implements DiscountStrategy {
public double calculateActualAmount(double totalPrice) {
return 0.8 * totalPrice;
}
}
優惠策略C對應滿99減50:
package com.dcc.openTalk.designPattern.strategyPattern.useDesignPattern;
/**
* 滿99減50
*
* @author dxc
* @date 2019/3/31
*/
public class DiscountStrategyC implements DiscountStrategy{
public double calculateActualAmount(double totalPrice) {
if (totalPrice >= 99) {
totalPrice = totalPrice - 50;
}
return totalPrice;
}
}
策略上下文,維護一個對策略對象的引用:
package com.dcc.openTalk.designPattern.strategyPattern.useDesignPattern;
/**
* 策略上下文
* @author dxc
* @date 2019/3/31
*/
public class StrategyContext {
private DiscountStrategy discountStrategy;
public StrategyContext(DiscountStrategy discountStrategy){
this.discountStrategy = discountStrategy;
}
public double getActualPrice(double totalPrice){
return discountStrategy.calculateActualAmount(totalPrice);
}
}
客戶端調用類:
package com.dcc.openTalk.designPattern.strategyPattern.useDesignPattern;
/**
* 客戶端調用
* @author dxc
* @date 2019/3/31
*/
public class Client {
public static void main(String []args){
StrategyContext context = new StrategyContext(new DiscountStrategyB());
double actualPrice = context.getActualPrice(2019.00);
System.out.println("優惠後實付:" + actualPrice);
}
}
寫到這裏基本上,就是策略模式的大致結構了。細心的讀者可能會發現,上面的Client類代碼中需要客戶端自己根據所屬的優惠類型去選擇實例化哪種優惠策略,是否可以把這部分邏輯放到服務方呢。這當然是可以的,真正在項目中使用時,客戶端只要告訴服務端當前優惠類型就可以了,具體使用哪種策略由服務方來匹配並給出結果,可以通過配置(xml配置文件或者註解配置)。也有利用反射結合策略模式的。
我們以在SpringBoot的項目中爲例,介紹配置方式的實現:
三種策略bean:
/**
* 原價處理
*
* @author dxc
* @date 2019/3/31
*/
@Service("discountStrategyA")
public class DiscountStrategyA implements DiscountStrategy {
public double calculateActualAmount(double totalPrice) {
return totalPrice;
}
}
/**
* 打八折優惠策略
*
* @author dxc
* @date 2019/3/31
*/
@Service("discountStrategyB")
public class DiscountStrategyB implements DiscountStrategy {
public double calculateActualAmount(double totalPrice) {
return 0.8 * totalPrice;
}
}
package com.dcc.openTalk.designPattern.strategyPattern.springProject;
import com.dcc.openTalk.designPattern.strategyPattern.useDesignPattern.DiscountStrategy;
import org.springframework.stereotype.Service;
/**
* 滿99減50
*
* @author dxc
* @date 2019/3/31
*/
@Service("discountStrategyC")
public class DiscountStrategyC implements DiscountStrategy{
public double calculateActualAmount(double totalPrice) {
if (totalPrice >= 99) {
totalPrice = totalPrice - 50;
}
return totalPrice;
}
}
在策略上下文中使用map存儲打折類型和打折具體策略類:
package com.dcc.openTalk.designPattern.strategyPattern.springProject;
import com.dcc.openTalk.designPattern.strategyPattern.useDesignPattern.DiscountStrategy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Map;
/**
* 策略上下文
* @author dxc
* @date 2019/3/31
*/
@Component("strategyContext")
public class StrategyContext {
@Resource(name = "discountStrategyMap")
private Map<Integer,DiscountStrategy> discountStrategyMap;
public double doAction(int type, double totalPrice) {
return this.discountStrategyMap.get(type).calculateActualAmount(totalPrice);
}
}
配置類:
package com.dcc.openTalk.designPattern.strategyPattern.springProject;
import com.dcc.openTalk.designPattern.strategyPattern.useDesignPattern.DiscountStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* @author dxc
* @date 2019/4/15 0015
*/
@Configuration
public class Config {
@Autowired
private DiscountStrategyA discountStrategyA;
@Autowired
private DiscountStrategyB discountStrategyB;
@Autowired
private DiscountStrategyC discountStrategyC;
@Bean
public Map<Integer, DiscountStrategy> discountStrategyMap(){
Map<Integer, DiscountStrategy> map = new HashMap<>();
map.put(1, discountStrategyA);
map.put(2, discountStrategyB);
map.put(3, discountStrategyC);
return map;
}
}
測試類:
package com.dcc.openTalk.designPattern.strategyPattern.springProject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author dxc
* @date 2019/4/15 0015
*/
@RestController
@RequestMapping("/strategyPattern")
public class TestController {
@Autowired
private StrategyContext strategyContext;
@GetMapping("/test")
public double calculateActualAmount(@RequestParam(name = "totalPrice") double totalPrice,
@RequestParam(name = "type") int type){
return strategyContext.doAction(type, totalPrice);
}
}
本地啓動SpringBoot項目,測試如下:
代碼地址:https://github.com/WhiteBookMan1994/OpenTalk/tree/master/designPattern