使用策略模式重構電商折扣和支付場景

Tom彈架構發表於2021-11-05

本文節選自《設計模式就該這樣學》

1 使用策略模式實現促銷優惠方案選擇

大家都知道,咕泡學院的架構師課程經常會有優惠活動,優惠策略有很多種可能,如領取優惠券抵扣、返現促銷、拼團優惠等。下面用程式碼來模擬,首先建立一個促銷策略的抽象PromotionStrategy。


/**
 * 促銷策略抽象
 * Created by Tom.
 */
public interface IPromotionStrategy {
    void doPromotion();
}

然後分別建立優惠券抵扣策略CouponStrategy類、返現促銷策略CashbackStrategy類、拼團優惠策略GroupbuyStrategy類和無優惠策略EmptyStrategy類。
CouponStrategy類的程式碼如下。


public class CouponStrategy implements IPromotionStrategy {
    public void doPromotion() {
        System.out.println("使用優惠券抵扣");
    }
}

CashbackStrategy類的程式碼如下。


public class CashbackStrategy implements IPromotionStrategy {
    public void doPromotion() {
        System.out.println("返現,直接打款到支付寶賬號");
    }
}

GroupbuyStrategy類的程式碼如下。


public class GroupbuyStrategy implements IPromotionStrategy {
    public void doPromotion() {
        System.out.println("5人成團,可以優惠");
    }
}

EmptyStrategy類的程式碼如下。


public class EmptyStrategy implements IPromotionStrategy {
    public void doPromotion() {
        System.out.println("無優惠");
    }
}

接著建立促銷活動方案PromotionActivity類。


public class PromotionActivity {
    private IPromotionStrategy strategy;

    public PromotionActivity(IPromotionStrategy strategy) {
        this.strategy = strategy;
    }

    public void execute(){
        strategy.doPromotion();
    }
}

最後編寫客戶端測試程式碼。


public static void main(String[] args) {
    PromotionActivity activity618 = new PromotionActivity(new CouponStrategy());
    PromotionActivity activity1111 = new PromotionActivity(new CashbackStrategy());

    activity618.execute();
    activity1111.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 PromotionStrategyFacory {

    private static Map<String,IPromotionStrategy> PROMOTIONS = new HashMap<String, IPromotionStrategy>();

    static {
        PROMOTIONS.put(PromotionKey.COUPON,new CouponStrategy());
        PROMOTIONS.put(PromotionKey.CASHBACK,new CashbackStrategy());
        PROMOTIONS.put(PromotionKey.GROUPBUY,new GroupbuyStrategy());
    }

    private static final IPromotionStrategy EMPTY = new EmptyStrategy();

    private PromotionStrategyFacory(){}

    public static IPromotionStrategy getPromotionStrategy(String promotionKey){
        IPromotionStrategy strategy = PROMOTIONS.get(promotionKey);
        return strategy == null ? EMPTY : strategy;
    }
    private interface PromotionKey{
        String COUPON = "COUPON";
        String CASHBACK = "CASHBACK";
        String GROUPBUY = "GROUPBUY";
    }

    public static  Set<String> getPromotionKeys(){
        return PROMOTIONS.keySet();
    }
}

這時候,客戶端測試程式碼如下。


public static void main(String[] args) {
        PromotionStrategyFacory.getPromotionKeys();
        String promotionKey = "COUPON";

        IPromotionStrategy promotionStrategy = PromotionStrategyFacory.getPromotionStrategy (promotionKey);
        promotionStrategy.doPromotion();
}

程式碼優化之後,程式設計師的維護工作也變得輕鬆了。每次上新活動都不影響原來的程式碼邏輯。

2 使用策略模式重構支付方式選擇場景

為了加深對策略模式的理解,我們再舉一個案例。相信小夥伴們都用過支付寶、微信支付、銀聯支付及京東白條,一個常見的應用場景就是大家在下單支付時會提示選擇支付方式,如果使用者未選,系統也會預設好推薦的支付方式進行結算。來看如下圖所示的類圖,我們用策略模式來模擬此業務場景。

file

首先建立Payment抽象類,定義支付規範和支付邏輯,程式碼如下。


import com.tom.pattern.strategy.pay.PayState;

/**
 * 支付渠道
 * Created by Tom.
 */
public abstract class Payment {

    public abstract String getName();

    //通用邏輯被放到抽象類裡實現
    public MsgResult pay(String uid, double amount){
        //餘額是否足夠
        if(queryBalance(uid) < amount){
            return new MsgResult(500,"支付失敗","餘額不足");
        }
        return new MsgResult(200,"支付成功","支付金額" + amount);
    }

    protected abstract double queryBalance(String uid);
}

然後分別建立具體的支付方式,支付寶AliPay類的程式碼如下。


public class AliPay extends Payment {
    public String getName() {
        return "支付寶";
    }

    protected double queryBalance(String uid) {
        return 900;
    }
}

京東白條JDPay類的程式碼如下。



public class JDPay extends Payment {
    public String getName() {
        return "京東白條";
    }

    protected double queryBalance(String uid) {
        return 500;
    }
}

微信支付WechatPay類的程式碼如下。


public class WechatPay extends Payment {
    public String getName() {
        return "微信支付";
    }

    protected double queryBalance(String uid) {
        return 263;
    }
}

銀聯支付UnionPay類的程式碼如下。


public class UnionPay extends Payment {
    public String getName() {
        return "銀聯支付";
    }

    protected double queryBalance(String uid) {
        return 120;
    }
}

接著建立支付狀態的包裝類MsgResult。


/**
 * 支付完成以後的狀態
 * Created by Tom.
 */
public class MsgResult {
    private int code;
    private Object data;
    private String msg;

    public MsgResult(int code, String msg, Object data) {
        this.code = code;
        this.data = data;
        this.msg = msg;
    }

    @Override
    public String toString() {
        return "MsgResult{" +
                "code=" + code +
                ", data=" + data +
                ", msg='" + msg + '\'' +
                '}';
    }
}

建立支付策略管理類。


import java.util.HashMap;
import java.util.Map;

/**
 * 支付策略管理
 * Created by Tom.
 */
public class PayStrategy {
    public static  final String ALI_PAY = "AliPay";
    public static  final String JD_PAY = "JdPay";
    public static  final String WECHAT_PAY = "WechatPay";
    public static  final String UNION_PAY = "UnionPay";
    public static  final String DEFAULT_PAY = ALI_PAY;

    private static Map<String,Payment> strategy = new HashMap<String,Payment>();

    static {
        strategy.put(ALI_PAY,new AliPay());
        strategy.put(JD_PAY,new JDPay());
        strategy.put(WECHAT_PAY,new WechatPay());
        strategy.put(UNION_PAY,new UnionPay());
    }

    public static Payment get(String payKey){
        if(!strategy.containsKey(payKey)){
            return strategy.get(DEFAULT_PAY);
        }
        return strategy.get(payKey);
    }
}

建立訂單Order類。


import com.tom.pattern.strategy.pay.payport.PayStrategy;
import com.tom.pattern.strategy.pay.payport.Payment;

/**
 * Created by Tom.
 */
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 MsgResult pay(){
        return pay(PayStrategy.DEFAULT_PAY);
    }

    public MsgResult pay(String payKey){
        Payment payment = PayStrategy.get(payKey);
        System.out.println("歡迎使用" + payment.getName());
        System.out.println("本次交易金額為" + amount + ",開始扣款");
        return payment.pay(uid,amount);
    }
}

最後編寫客戶端測試程式碼。


public static void main(String[] args) {
        Order order = new Order("1","2020031401000323",324.5);
        System.out.println(order.pay(PayStrategy.ALI_PAY));
}

執行結果如下圖所示。

file

通過常見的業務場景舉例,希望小夥伴們能夠更深刻地理解策略模式。

【推薦】Tom彈架構:收藏本文,相當於收藏一本“設計模式”的書

本文為“Tom彈架構”原創,轉載請註明出處。技術在於分享,我分享我快樂!
如果本文對您有幫助,歡迎關注和點贊;如果您有任何建議也可留言評論或私信,您的支援是我堅持創作的動力。關注微信公眾號『 Tom彈架構 』可獲取更多技術乾貨!

相關文章