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