提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
博文简介
通过对策略模式的学习,消除程序中大量的冗余代码和多重条件转移语句。
策略模式的定义及应用场景
策略模式的定义
策略模式(Strategy Pattern)是指定义了算法家族、分别封装起来,让它们之间可以互 相替换,此模式让算法的变化不会影响到使用算法的用户。
策略模式的应用场景
1、假如系统中有很多类,而他们的区别仅仅在于他们的行为不同。
2、一个系统需要动态地在几种算法中选择一种。
策略模式实现
大家都知道,网络上的各种培训课程经常会有优惠活动,优惠策略会有很多种可能如:领取优惠券抵扣、返现促销、拼团优惠。下面我们用代码来模拟这种场景。
首先我们创建一 个促销策略的抽象PromotionStrategy:
/*** 促销策略抽象 */
public interface PromotionStrategy {
void doPromotion();
}
然后分别创建优惠券抵扣策略 CouponStrategy 类、返现促销策略 CashbackStrategy 类、拼团优惠策略 GroupbuyStrategy 类和无优惠策略 EmptyStrategy 类。
CouponStrategy 类:
/*** 优惠券 **/
public class CouponStrategy implements PromotionStrategy {
public void doPromotion() {
System.out.println("领取优惠券,课程的价格直接减优惠券面值抵扣");
}
}
CashbackStrategy 类:
/*** 返现活动 */
public class CashbackStrategy implements PromotionStrategy {
public void doPromotion() {
System.out.println("返现促销,返回的金额转到支付宝账号");
}
}
GroupbuyStrategy 类:
/*** 拼团优惠 */
public class GroupbuyStrategy implements PromotionStrategy{
public void doPromotion() {
System.out.println("拼团,满 20 人成团,全团享受团购价");
}
}
EmptyStrategy 类:
/*** 无优惠 **/
public class EmptyStrategy implements PromotionStrategy {
public void doPromotion() {
System.out.println("无促销活动");
}
}
然后创建促销活动方案 PromotionActivity 类:
/*** 优惠活动 **/
public class PromotionActivity {
private PromotionStrategy promotionStrategy;
public PromotionActivity(PromotionStrategy promotionStrategy) {
this.promotionStrategy = promotionStrategy;
}
public void execute(){
promotionStrategy.doPromotion();
}
}
编写客户端测试类:
public static void main(String[] args) {
PromotionActivity activity1 = new PromotionActivity(new CouponStrategy());
PromotionActivity activity2 = new PromotionActivity(new CashbackStrategy());
activity1.execute();
activity2.execute();
}
此时,同学们会发现,如果把上面这段测试代码放到实际的业务场景其实会被骂。 因为我们做活动时候往往是要根据不同的需求对促销策略进行动态选择的,并不会一次性执行多种优惠。所以,我们的代码通常会这样写:
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 类:
/*** 促销策略工厂 **/
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";
}
}
这时候我们客户端代码就应该这样写:
public static void main(String[] args) {
String promotionKey = "GROUPBUY";
PromotionActivity promotionActivity = new PromotionActivity(PromotionStrategyFactory.getPromotionStrategy(promotionKey));
promotionActivity.execute();
}
代码优化之后,是不是我们作为加班猿的概率就又降低了一点,每次上新活动,不影响原来的代码逻辑。效率大大的提高了。
选择支付方式的业务场景
为了加深对策略模式的理解,我们再来举一个案例。相信小伙伴们都 用过支付宝、微信支付、银联支付以及京东白条。一个常见的应用场景就是大家在下单 支付时会提示选择支付方式,如果用户未选,系统也会默认好推荐的支付方式进行结算。 下面我们用策略模式来模拟此业务场景:
创建 Payment 抽象类,定义支付规范和支付逻辑,代码如下:
/*** 支付渠道 **/
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);
}
}
分别创建具体的支付方式
京东白条 JDPay 类:
public class JDPay extends Payment {
public String getName() {
return "京东白条";
}
protected double queryBalance(String uid) {
return 600;
}
}
支付宝 AliPay 类:
public class AliPay extends Payment {
public String getName() {
return "支付宝";
}
protected double queryBalance(String uid) {
return 1000;
}
}
微信支付 WechatPay 类:
public class WechatPay extends Payment {
public String getName() {
return "微信支付";
}
protected double queryBalance(String uid) {
return 300;
}
}
银联支付 UnionPay 类:
public class UnionPay extends Payment {
public String getName() {
return "银联支付";
}
protected double queryBalance(String uid) {
return 150;
}
}
创建支付状态的包装类 PayState:
/*** 支付完成以后的状态 **/
public class PayState {
private int code;
private Object data;
private String msg;
public PayState(int code, String msg,Object data) {
this.code = code;
this.data = data;
this.msg = msg;
}
public String toString(){
return ("支付状态:[" + code + "]," + msg + ",交易详情:" + data);
}
}
创建支付策略管理类PayStrategy:
/*** 支付策略管理 **/
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 类:
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;
}
//完美地解决了 switch 的过程,不需要在代码逻辑中写 switch 了
//更不需要写 if else if
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);
}
}
编写测试代码:
public class PayStrategyTest {
public static void main(String[] args) {
//省略把商品添加到购物车,再从购物车下单
//直接从点单开始
Order order = new Order("1","20201005001000009",300.99);
//开始支付,选择微信支付、支付宝、银联卡、京东白条、财付通
//每个渠道它支付的具体算法是不一样的
//基本算法固定的
//这个值是在支付的时候才决定用哪个值
System.out.println(order.pay(PayStrategy.ALI_PAY));
}
}
这里通过大家比较熟悉的业务场景来举例,让同学们更深刻地理解策略模式。希望大家在以后的工作体现出自己的优势。
策略模式在 JDK 源码中的体现
首先来看一个比较常用的比较器 Comparator 接口,我们看到的一个大家常用的 compare()方法,就是一个策略抽象实现:
public interface Comparator<T> {
int compare(T o1, T o2);
...
}
Comparator抽象下面有非常多的实现类,我们经常会把Comparator作为参数传入作为排序策略,例如 Arrays类的parallelSort方法等:
public class Arrays {
...
public static <T> void parallelSort(T[] a, int fromIndex, int toIndex, Comparator<? super T> cmp) {
...
}
...
}
还有TreeMap的构造方法:
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable {
...
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
...
}
策略模式的优缺点
优点:
1、策略模式符合开闭原则。
2、避免使用多重条件转移语句,如 if…else…语句、switch 语句
3、使用策略模式可以提高算法的保密性和安全性。
缺点:
1、客户端必须知道所有的策略,并且自行决定使用哪一个策略类。
2、代码中会产生非常多策略类,增加维护难度。