策略模式
概述
策略模式作为一种软件设计模式,指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。比如每个人都要“交个人所得税”,但是“在美国交个人所得税”和“在中国交个人所得税”就有不同的算税方法。
- 定义了一组算法(业务规则)
- 封装了每个算法
- 这族的算法可互相代替
示例程序
在商品详情页面,商家为了激起群众的购买欲望,经常会发送一些优惠券,比如:满减、直减、折扣、n元购等优惠券,在本案例中,我们模拟在购买商品时使用的各种类型的优惠券。
不使用设计模式实现
不使用设计模式,使用if-else对不同的优惠券进行判断,代码如下
public class CouponDiscountService {
/**
*
* @param type 优惠类型
* @param typeContent 优惠力度
* @param skuPrice 原价
* @param typeExt 满减券使用金额
* @return
*/
public double discountAmount(int type, double typeContent, double skuPrice, double typeExt) {
// 1. 直减券
if (1 == type) {
return skuPrice - typeContent;
}
// 2. 满减券
if (2 == type) {
if (skuPrice < typeExt) return skuPrice;
return skuPrice - typeContent;
}
// 3. 折扣券
if (3 == type) {
return skuPrice * typeContent;
}
// 4. n元购
if (4 == type) {
return typeContent;
}
return 0D;
}
}
使用策略模式
上述的代码不利于后续的维护,我们通过策略模式进行修改。
优惠接口
public interface ICouponDiscount<T> {
/**
* 优惠券金额计算
* @param couponInfo 券折扣信息;直减、满减、折扣、N元购
* @param skuPrice sku金额
* @return 优惠后金额
*/
BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice);
}
各类优惠实现类
//满减
public class MJCouponDiscount implements ICouponDiscount<Map<String,String>> {
/**
*
* @param couponInfo 满减,满x元-n元
* @param skuPrice sku金额
* @return
*/
@Override
public BigDecimal discountAmount(Map<String, String> couponInfo, BigDecimal skuPrice) {
String x = couponInfo.get("x");
String n = couponInfo.get("n");
//不满足满减
if (skuPrice.compareTo(new BigDecimal(x)) < 0){
return skuPrice;
}
BigDecimal bigDecimal = skuPrice.subtract(new BigDecimal(n));
if (bigDecimal.compareTo(BigDecimal.ZERO) < 0){
return BigDecimal.ONE;
}
return bigDecimal;
}
}
//直减
public class ZJCouponDiscount implements ICouponDiscount<Double> {
@Override
public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
BigDecimal bigDecimal = skuPrice.subtract(new BigDecimal(couponInfo));
if (bigDecimal.compareTo(BigDecimal.ZERO) < 0){
return BigDecimal.ONE;
}
return bigDecimal;
}
}
//折扣
public class ZKCouponDiscount implements ICouponDiscount<Double> {
/**
*
* @param couponInfo 折扣 0.x
* @param skuPrice sku金额
* @return
*/
@Override
public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
BigDecimal multiply = skuPrice.multiply(new BigDecimal(couponInfo)).setScale(2);
return multiply;
}
}
//n元购
public class NYGCouponDiscount implements ICouponDiscount<Double> {
@Override
public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
return new BigDecimal(couponInfo);
}
}
策略控制类
策略模式的控制类主要是外部可以传递不同的策略实现,在通过统一的方法执行优惠策略计算。
public class Context<T> {
private ICouponDiscount<T> iCouponDiscount;
public Context(ICouponDiscount<T> iCouponDiscount) {
this.iCouponDiscount = iCouponDiscount;
}
public BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice) {
return iCouponDiscount.discountAmount(couponInfo,skuPrice);
}
}
测试
@Slf4j
@SpringBootTest
class Practice2301ApplicationTests {
@Test
void contextLoads() {
Context<Map<String, String>> mapContext = new Context<>(new MJCouponDiscount());
HashMap<String, String> map = new HashMap<>();
map.put("x","100");
map.put("n","10");
BigDecimal bigDecimal = mapContext.discountAmount(map, new BigDecimal(100));
log.info("商品原价:{},优惠后:{}",new BigDecimal(100),bigDecimal);
}
}
//结果
//商品原价:100,优惠后:90
登场角色
-
Strategy(策略)
Strategy角色负责决定实现策略所必须的接口,在示例程序中由ICouponDiscount扮演
-
ConcreteStrategy(具体的策略)
ConcreteStrategy负责实现Strategy角色定义的方法,在示例程序中由MJCouponDiscount,ZJCouponDiscount,ZKCouponDiscount,NYGCouponDiscount扮演
-
Context(控制类)
负责使用Strategy角色,Context还保存了ConcreteStrategy实例,并使有ConcreteStrategy角色去实现需求,在示例程序中由Context扮演
UML类图
总结
为什么要特意编写Strategy角色?
虽然看起来程序好像变复杂了,其实不然。例如我们想要通过改善算法来提高算法的处理速度时,如果使用了Strategy模式,就不必修改Strategy角色的接口,仅仅只需要修改ConcreteStrategy角色即可。而且使用委托这种弱关联关系可以很方便的整体替换算法。
优点
-
策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码转移到父类里面,从而避免重复的代码。
-
策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。
-
使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
缺点
-
客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
-
策略模式造成很多的策略类,每个具体策略类都会产生一个新类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。