策略模式(Strategy Pattern)是指定义了算法家族、分别封装起来,让它们之间可以互相替换,此模式让算法的变化不会影响到使用算法的用户。
- 假如系统中有很多类,而他们的区别仅仅在于他们的行为不同。
- 一个系统需要动态地在几种算法中选择一种
大家都知道,电商网站经常会有优惠活动,优惠策略会有很多种可能,如:领取优惠券抵扣、返现促销、拼团优惠。下面我们用代码模拟,首先我们创建一个促销策略的抽象PromotionStrategy:
package com.wenbin.design.pattern.strategy.promotion;
public interface PromotionStrategy {
void doPromotion();
}
然后分别创建优惠券抵扣策略CouponStrategy类、返现促销类CashbackStrategy类、拼团优惠策略GroupbyStrategy类和无优惠策略EmptyStrategy类:
CouponStrategy类:
package com.wenbin.design.pattern.strategy.promotion;
public class CouponStrategy implements PromotionStrategy {
@Override
public void doPromotion() {
System.out.println("领取优惠券,课程的价格直接减优惠券面额抵扣");
}
}
CashbackStrategy类:
package com.wenbin.design.pattern.strategy.promotion;
public class CashbackStrategy implements PromotionStrategy {
@Override
public void doPromotion() {
System.out.println("返现促销,返回的金额转到支付宝账号");
}
}
GroupbuyStrategy类:
package com.wenbin.design.pattern.strategy.promotion;
public class GroupbuyStrategy implements PromotionStrategy {
@Override
public void doPromotion() {
System.out.println("拼团,满20人成团,全团享受团购价");
}
}
EmptyStrategy类:
package com.wenbin.design.pattern.strategy.promotion;
public class EmptyStrategy implements PromotionStrategy {
@Override
public void doPromotion() {
System.out.println("无促销活动");
}
}
然后创建促销活动方案PromotionActivity类:
package com.wenbin.design.pattern.strategy.promotion;
public class PromotionActivity {
private PromotionStrategy promotionStrategy;
public PromotionActivity(PromotionStrategy promotionStrategy) {
this.promotionStrategy = promotionStrategy;
}
public void execute() {
promotionStrategy.doPromotion();
}
}
编写客户端测试类:
package com.wenbin.design.pattern.strategy.promotion;
public class PromotionStrategyTest {
public static void main(String[] args) {
PromotionActivity activity618 = new PromotionActivity(new CouponStrategy());
PromotionActivity activity1111 = new PromotionActivity(new CashbackStrategy());
activity618.execute();
activity1111.execute();
}
}
此时,如果把上面这段测试代码放到实际的业务场景其实并不实用。因为做活动的时候往往是要根据不同的需求对促销策略进行动态选择,并不会一次性执行多种优惠。所以通常会这样写:
package com.wenbin.design.pattern.strategy.promotion;
import org.apache.commons.lang3.StringUtils;
public class PromotionStrategyTest2 {
public static void main(String[] args) {
PromotionActivity promotionActivity = null;
String promotionKey = "COUPON";
if (StringUtils.equals(promotionKey, "COUPON")) {
promotionActivity = new PromotionActivity(new CouponStrategy());
} else if (StringUtils.equals(promotionKey, "CASHBACK")) {
promotionActivity = new PromotionActivity(new CashbackStrategy());
}
promotionActivity.execute();
}
}
这样改造之后,满足了业务需求,客户可根据自己的需求选择不同的优惠策略。但是随着促销种类的越来越多,每次上线新的活动都需要改代码并且重复测试,判断逻辑会变得越来越复杂。着时候就应该考虑重构了。我们可以结合单例模式和工厂模式,来重构代码:
创建PromotionStrategyFactory类:
package com.wenbin.design.pattern.strategy.promotion;
import java.util.HashMap;
import java.util.Map;
public class PromotionStrategyFactory {
private static Map<String, PromotionStrategy> PROMOTION_STRATEGY_MAP = new HashMap<String, PromotionStrategy>();
static {
PROMOTION_STRATEGY_MAP.put(PromotionKey.COUPON, new CouponStrategy());
PROMOTION_STRATEGY_MAP.put(PromotionKey.CASHBACK, new CashbackStrategy());
PROMOTION_STRATEGY_MAP.put(PromotionKey.GROUPBUY, new GroupbuyStrategy());
}
private static final PromotionStrategy NON_PROMOTION = new EmptyStrategy();
private PromotionStrategyFactory() { }
public static PromotionStrategy getPromotionStrategy(String promotionKey) {
PromotionStrategy promotionStrategy = PROMOTION_STRATEGY_MAP.get(promotionKey);
return promotionStrategy == null ? NON_PROMOTION : promotionStrategy;
}
private interface PromotionKey {
String COUPON = "COUPON";
String CASHBACK = "CASHBACK";
String GROUPBUY = "GROUPBUY";
}
}
着时候我们客户端代码就应该这样写了:
package com.wenbin.design.pattern.strategy.promotion;
public class PromotionStrategyTest3 {
public static void main(String[] args) {
String promotionKey = "COUPON";
PromotionActivity promotionActivity = new PromotionActivity(PromotionStrategyFactory.getPromotionStrategy(promotionKey));
promotionActivity.execute();
}
}
代码优化之后,维护工作就会轻松很多,每次上新活动,不影响原来的代码逻辑。测试也只需要测试新上的活动。老活动不用再回归测试了。
还有一个常见的应用场景就是大家在下单支付的时候会提示选择支付方式,如果用户未选,系统也会默认好推荐的支付方式进行结算。我们用这个场景来加深对策略模式的理解。
创建Payment抽象类,定义支付规范和支付逻辑,代码如下:
package com.wenbin.design.pattern.strategy.pay;
public abstract class Payment {
// 支付类型
public abstract String getName();
// 查询余额
protected abstract double queryBalance(String uid);
// 扣款支付
public PayState pay(String uid, double amount) {
if (queryBalance(uid) < amount) {
return new PayState(500, "支付失败", "余额不足");
}
return new PayState(200, "支付成功", "支付金额:" + amount);
}
}
支付宝AliPay类:
package com.wenbin.design.pattern.strategy.pay;
public class AliPay extends Payment {
@Override
public String getName() {
return "支付宝";
}
@Override
protected double queryBalance(String uid) {
return 900;
}
}
京东白条JDPay类:
package com.wenbin.design.pattern.strategy.pay;
public class JDPay extends Payment {
@Override
public String getName() {
return "京东白条";
}
@Override
protected double queryBalance(String uid) {
return 500;
}
}
微信支付WechatPay类:
package com.wenbin.design.pattern.strategy.pay;
public class WechatPay extends Payment {
@Override
public String getName() {
return "微信支付";
}
@Override
protected double queryBalance(String uid) {
return 256;
}
}
银联支付 UnionPay 类:
package com.wenbin.design.pattern.strategy.pay;
public class UnionPay extends Payment {
@Override
public String getName() {
return "银联支付";
}
@Override
protected double queryBalance(String uid) {
return 120;
}
}
创建支付状态的包装类 PayState:
package com.wenbin.design.pattern.strategy.pay;
public class PayState {
private int code;
private Object data;
private String msg;
public PayState(int code, Object data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
}
public String toString() {
return ("支付状态:[" + code + "]," + msg + ",交易详情:" + data);
}
}
创建支付策略管理类:
package com.wenbin.design.pattern.strategy.pay;
import java.util.HashMap;
import java.util.Map;
public class PayStrategy {
public static final String ALI_PAY = "AliPay";
public static final String JD_PAY = "JDPay";
public static final String UNION_PAY = "unionPay";
public static final String WECHAT_PAY = "wechatPay";
public static final String DEFAULT_PAY = ALI_PAY;
private static Map<String, Payment> payStrategy = new HashMap<String, Payment>();
static {
payStrategy.put(ALI_PAY, new AliPay());
payStrategy.put(WECHAT_PAY, new WechatPay());
payStrategy.put(UNION_PAY, new UnionPay());
payStrategy.put(JD_PAY, new JDPay());
}
public static Payment get(String payKey) {
if (!payStrategy.containsKey(payKey)) {
return payStrategy.get(DEFAULT_PAY);
}
return payStrategy.get(payKey);
}
}
创建订单 Order 类:
package com.wenbin.design.pattern.strategy.pay;
public class Order {
private String uid;
private String orderId;
private double amount;
public Order(String uid, String orderId, double amount) {
this.uid = uid;
this.orderId = orderId;
this.amount = amount;
}
public PayState pay() {
return pay(PayStrategy.DEFAULT_PAY);
}
public PayState pay(String payKey) {
Payment payment = PayStrategy.get(payKey);
System.out.println("欢迎使用" + payment.getName());
System.out.println("本次交易金额为:" + amount + ",开始扣款...");
return payment.pay(uid, amount);
}
}
测试代码:
package com.wenbin.design.pattern.strategy.pay;
public class PayStrategyTest {
public static void main(String[] args) {
// 直接从下单开始
Order order = new Order("1", "201811112313131", 324.56);
// 开始支付,选择微信支付、支付宝、银联卡、京东白条、财付通
// 每个渠道它支付的具体过程是不一样的
System.out.println(order.pay(PayStrategy.ALI_PAY));
}
}
运行结果:
优点:
- 策略模式符合开闭原则。
- 避免使用多重条件转移语句,如if...else...语句、switch语句
- 使用策略模式可以提高算法的保密性和安全性。
缺点:
- 客户端必须知道所有的策略,并且自行决定使用哪一个策略类。
- 代码中会产生非常多的策略类,增加维护难度。