談笑間學會策略模式

1 背景

某天早上,Skr郎正在一邊悠哉的喫着加了三個雞蛋的手抓餅,一邊悠閒地逛着論壇,看着沙雕網友的帖子,Skr郎會心一笑,正欲給沙雕帖子點贊,郵件忽的彈出,Skr郎慢悠悠的打開郵件

任務: 對接阿里支付接口
時限: 一週

要求如下:
	1. 單筆限額不超過1W
	2. 可以成功提現
	3. 發起提現後,可以主動查詢提現結果

Skr郎心中暗喜,看我兩天搞定,剩下的時間就可以嘿嘿嘿了

/**
 * 阿里業務處理
 *
 * @author gp6
 * @date 2020/3/13
 */
public class AlibabaHandler {

    void singleQuota() {
        System.out.println("阿里校驗單筆限額");
    }

    void pay() {
        System.out.println("阿里支付邏輯");
    }

    void getResult() {
        System.out.println("阿里主動查詢支付結果");
    }
}


/**
 * 支付服務類
 *
 * @author gp6
 * @date 2020/3/13
 */
public class PayService {

    public static void main(String[] args) {
       // 支付接口(第一個方法)
        AlibabaHandler alibabaHandler = new AlibabaHandler();
        alibabaHandler.singleQuota();
        alibabaHandler.pay();

        // 查詢接口(第二個方法)
        AlibabaHandler alibabaHandler1 = new AlibabaHandler();
        alibabaHandler1.getResult();
    }
}

Skr郎看着完成的代碼,心中評價道:這段代碼,邏輯嚴謹,註釋清晰,優雅中透露着灑脫,灑脫中透露着不羈,完美!

正洋洋自得間,項目經理喊道:Skr郎,你過來一下!Skr郎趕緊屁顛屁顛的跑過去.

項目經理:臨時加個需求,把微信支付也接進來,根據前臺傳的渠道,使用不同的支付方式,時間不加.

Skr郎:好的,沒問題(MMP)

/**
 * 微信業務處理
 *
 * @author gp6
 * @date 2020/3/13
 */
public class WechatHandler {

    void singleQuota() {
        System.out.println("微信校驗單筆限額");
    }

    void pay() {
        System.out.println("微信支付邏輯");
    }

    void getResult() {
        System.out.println("微信主動查詢支付結果");
    }
}


/**
 * 支付服務類
 *
 * @author gp6
 * @date 2020/3/13
 */
public class PayService {

    public static void main(String[] args) {
        // 前臺傳的渠道 1:阿里 2:微信
        String channel = "1";

        // 支付邏輯(第一個方法)
        if ("1".equals(channel)) {
            // 阿里支付接口
            AlibabaHandler alibabaHandler = new AlibabaHandler();
            alibabaHandler.singleQuota();
            alibabaHandler.pay();
        } else if ("2".equals(channel)) {
            // 微信支付接口
            WechatHandler wechatHandler = new WechatHandler();
            wechatHandler.singleQuota();
            wechatHandler.pay();
        }


        // 查詢接口(第二個方法)
        if ("1".equals(channel)) {
            // 阿里查詢
            AlibabaHandler alibabaHandler = new AlibabaHandler();
            alibabaHandler.getResult();
        } else if ("2".equals(channel)) {
            // 微信查詢
            WechatHandler wechatHandler = new WechatHandler();
            wechatHandler.getResult();
        }
    }
}


Skr郎看着完成的代碼,心中評價道:這段代碼,邏輯嚴謹,註釋清晰,優雅中透露着灑脫,灑脫中透露着不羈,雖然沒有上一版優雅,但還是比較不錯的!

正洋洋自得間,又聽項目經理喊道:Skr郎,你過來一下!Skr郎心裏想着如果這次加需求不加時間,老子堅決不做!

項目經理:臨時在加個需求,把通聯和Ping++支付也接進來,根據前臺傳的渠道,使用不同的支付方式,時間不加.

Skr郎:好的,沒問題(心裏問候一下他的族中長輩)


/**
 * Ping++業務處理
 *
 * @author gp6
 * @date 2020/3/13
 */
public class PingHandler {

    void singleQuota() {
        System.out.println("Ping++校驗單筆限額");
    }

    void pay() {
        System.out.println("Ping++支付邏輯");
    }

    void getResult() {
        System.out.println("Ping++主動查詢支付結果");
    }
}


/**
 * 通聯業務處理
 *
 * @author gp6
 * @date 2020/3/13
 */
public class AllinpayHandler {

    void singleQuota() {
        System.out.println("通聯校驗單筆限額");
    }

    void pay() {
        System.out.println("通聯支付邏輯");
    }

    void getResult() {
        System.out.println("通聯主動查詢支付結果");
    }
}


/**
 * 支付服務類
 *
 * @author gp6
 * @date 2020/3/13
 */
public class PayService {

    public static void main(String[] args) {
        // 前臺傳的渠道 1:阿里 2:微信 3:Ping++ 4:通聯
        String channel = "1";

        // 支付邏輯(第一個方法)
        if ("1".equals(channel)) {
            // 阿里支付接口
            AlibabaHandler alibabaHandler = new AlibabaHandler();
            alibabaHandler.singleQuota();
            alibabaHandler.pay();
        } else if ("2".equals(channel)) {
            // 微信支付接口
            WechatHandler wechatHandler = new WechatHandler();
            wechatHandler.singleQuota();
            wechatHandler.pay();
        }else if ("3".equals(channel)) {
            // Ping++支付接口
            PingHandler pingHandler = new PingHandler();
            pingHandler.singleQuota();
            pingHandler.pay();
        }else if ("4".equals(channel)) {
            // 通聯支付接口
            AllinpayHandler allinpayHandler = new AllinpayHandler();
            allinpayHandler.singleQuota();
            allinpayHandler.pay();
        }


        // 查詢接口(第二個方法)
        if ("1".equals(channel)) {
            // 阿里查詢
            AlibabaHandler alibabaHandler = new AlibabaHandler();
            alibabaHandler.getResult();
        } else if ("2".equals(channel)) {
            // 微信查詢
            WechatHandler wechatHandler = new WechatHandler();
            wechatHandler.getResult();
        }else if ("3".equals(channel)) {
            // Ping++查詢
            PingHandler pingHandler = new PingHandler();
            pingHandler.getResult();
        }else if ("4".equals(channel)) {
            // 通聯查詢
            AllinpayHandler allinpayHandler = new AllinpayHandler();
            allinpayHandler.getResult();
        }
    }
}

Skr郎看着完成的代碼,心中評價道:這段代碼,邏輯嚴謹,註釋清晰…我呸,這寫的什麼鬼東西!看着長長的if else,Skr郎陷入沉思…

如果後面再加新的渠道,是不是每次都要改動PayService中的調用邏輯,而且如果有別的方法要用此段邏輯,if else 又要複製一遍

腦中過濾着以往所學的設計模式…突然,靈光一閃,策略模式

2 策略模式

定義:
	定義了一系列算法,並將每個算法封裝起來,使它們可以相互替換,且算法的變化不會影響使用算法的客戶

看了策略模式的定義,是不是很符合這種場景

定義了一系列算法(支付邏輯),並將每個算法(支付邏輯)封裝起來,使它們可以相互替換(根據不同channel切換不同的支付邏輯),且算法的變化不會影響使用算法的客戶(PayService)

簡直完美,Skr郎沾沾自喜

/**
 * 支付的抽象策略類
 * 抽象策略(Strategy)類:定義了一個公共接口,各種不同的算法以不同的方式實現這個接口,環境角色使用這個接口調用不同的算法,一般使用接口或抽象類實現
 *
 * @author gp6
 * @date 2020/3/13
 */
public interface PayHandler {

    /**
     * 單筆限額
     */
    void singleQuota();

    /**
     * 支付
     */
    void pay();

    /**
     * 提現結果查詢
     */
    void getResult();
}


/**
 * 微信業務處理
 * 具體策略(Concrete Strategy)類:實現了抽象策略定義的接口,提供具體的算法實現
 *
 * @author gp6
 * @date 2020/3/13
 */
public class WechatHandler implements PayHandler {
    public void singleQuota() {
        System.out.println("微信校驗單筆限額");
    }

    public void pay() {
        System.out.println("微信支付邏輯");
    }

    public void getResult() {
        System.out.println("微信主動查詢支付結果");
    }
}


/**
 * 阿里業務處理
 * 具體策略(Concrete Strategy)類:實現了抽象策略定義的接口,提供具體的算法實現
 *
 * @author gp6
 * @date 2020/3/13
 */
public class AlibabaHandler implements PayHandler {

    public void singleQuota() {
        System.out.println("阿里校驗單筆限額");
    }

    public void pay() {
        System.out.println("阿里支付邏輯");
    }

    public void getResult() {
        System.out.println("阿里主動查詢支付結果");
    }
}

通聯與Ping++代碼類似


/**
 * 環境(Context)類:持有一個策略類的引用,最終給客戶端調用
 *
 * @author gp6
 * @date 2020/3/13
 */
public class Context {

    private PayHandler payHandler;

    public Context(PayHandler payHandler){
        this.payHandler = payHandler;
    }

    /**
     * 單筆限額
     */
    void singleQuota() {
        payHandler.singleQuota();
    }

    /**
     * 支付
     */
    void pay() {
        payHandler.pay();
    }

    /**
     * 提現結果查詢
     */
    void getResult() {
        payHandler.getResult();
    }
}


/**
 * 支付服務類
 *
 * @author gp6
 * @date 2020/3/13
 */
public class PayService {

    public static void main(String[] args) {
        // 前臺傳的渠道 1:阿里 2:微信 3:Ping++ 4:通聯
        Integer channel = 1;

        // 支付邏輯(第一個方法)
        Context context;
        switch (channel) {
            case 1:
                context = new Context(new AlibabaHandler());
                break;
            case 2:
                context = new Context(new WechatHandler());
                break;
            case 3:
                context = new Context(new PingHandler());
                break;
            case 4:
                context = new Context(new AllinpayHandler());
                break;
            default:
                throw new RuntimeException("渠道不支持!");
        }
        context.singleQuota();
        context.pay();

        // 查詢接口(第二個方法)
        Context context2;
        switch (channel) {
            case 1:
                context2 = new Context(new AlibabaHandler());
                break;
            case 2:
                context2 = new Context(new WechatHandler());
                break;
            case 3:
                context2 = new Context(new PingHandler());
                break;
            case 4:
                context2 = new Context(new AllinpayHandler());
                break;
            default:
                throw new RuntimeException("渠道不支持!");
        }
        context2.getResult();
    }
}

使用了策略模式,但是調用者(PayService)中,還是有重複的大量的if else

根據不用的渠道創建不同的具體策略類(new AlibabaHandler()),這不是典型的工廠模式嗎…

3 策略模式結合工廠模式

將工廠模式放入策略環境中

/**
 * 環境(Context)類:持有一個策略類的引用,最終給客戶端調用
 *
 * @author gp6
 * @date 2020/3/13
 */
public class Context {

    private PayHandler payHandler;

    public Context(Integer channel){
        switch (channel) {
            case 1:
                payHandler = new AlibabaHandler();
                break;
            case 2:
                payHandler = new WechatHandler();
                break;
            case 3:
                payHandler = new PingHandler();
                break;
            case 4:
                payHandler = new AllinpayHandler();
                break;
            default:
                throw new RuntimeException("渠道不支持!");
        }
    }

    /**
     * 單筆限額
     */
    void singleQuota() {
        payHandler.singleQuota();
    }

    /**
     * 支付
     */
    void pay() {
        payHandler.pay();
    }

    /**
     * 提現結果查詢
     */
    void getResult() {
        payHandler.getResult();
    }
}


/**
 * 支付服務類
 *
 * @author gp6
 * @date 2020/3/13
 */
public class PayService {

    public static void main(String[] args) {
        // 前臺傳的渠道 1:阿里 2:微信 3:Ping++ 4:通聯
        Integer channel = 1;

        // 支付邏輯(第一個方法)
        Context context = new Context(channel);
        context.singleQuota();
        context.pay();

        // 查詢接口(第二個方法)
        Context context2  = new Context(channel);
        context2.getResult();
    }
}

此時如果再添加渠道,只需在Context的構造方法的判斷邏輯中添加新的渠道,然後在相應的支付邏輯類中寫入相應邏輯,只在策略環境中存在if else,調用者無需寫重複的if else


 switch (channel) {
    case 1:
        payHandler = new AlibabaHandler();
        break;
    case 2:
        payHandler = new WechatHandler();
        break;
    case 3:
        payHandler = new PingHandler();
        break;
    case 4:
        payHandler = new AllinpayHandler();
        break;

    // 新的渠道
    case 5:
    	payHandler = new XXXHandler();
    	break;
    default:
        throw new RuntimeException("渠道不支持!");
}

/**
 * 新渠道的業務邏輯處理
 * 具體策略(Concrete Strategy)類:實現了抽象策略定義的接口,提供具體的算法實現
 *
 * @author gp6
 * @date 2020/3/13
 */
public class XXXHandler implements PayHandler{
	.....
}

寫到此處Skr郎欣喜的點點頭,暗自得意,正打算將代碼提交,忽然眉頭一皺,每次添加新的渠道,都要修改判斷邏輯,不是違背了 開閉原則 嗎
Skr再次沉思

4 使用反射遵循開閉原則

/**
 * 環境(Context)類:持有一個策略類的引用,最終給客戶端調用
 *
 * @author gp6
 * @date 2020/3/13
 */
public class Context {

    private PayHandler payHandler;

    public Context(PayEnum payEnum){
        try {
            payHandler= (PayHandler) Class.forName(payEnum.getClassName()).newInstance();
        } catch (Exception e) {
            throw new RuntimeException("渠道錯誤!");
        }
    }


    /**
     * 單筆限額
     */
    void singleQuota() {
        payHandler.singleQuota();
    }

    /**
     * 支付
     */
    void pay() {
        payHandler.pay();
    }

    /**
     * 提現結果查詢
     */
    void getResult() {
        payHandler.getResult();
    }
}


/**
* 策略枚舉
*
* @author gp6
* @date 2020/3/13
*/
public enum PayEnum {

    /**
     * 渠道對應類全路徑
     */
    OPERATION_ADD("com.gp6.pay.AlibabaHandler", 1),
    OPERATION_SUB("com.gp6.pay.WechatHandler", 2),
    OPERATION_MUL("com.gp6.pay.PingHandler", 3);

    PayEnum(String className, Integer channel) {
        this.className = className;
        this.channel = channel;
    }

    /**
     * 類的全路徑
     */
    private String className;

    /**
     * 渠道
     */
    private Integer channel;

    /**
     * 匹配渠道
     *
     * @param channel 渠道
     * @return 相關枚舉
     */
    public static PayEnum matchChannel(Integer channel) {
        for (PayEnum payEnum : PayEnum.values()) {
            if (payEnum.getChannel().equals(channel)) {
                return payEnum;
            }
        }
        throw new RuntimeException("渠道不支持!");
    }

    public String getClassName() {
        return className;
    }

    public Integer getChannel() {
        return channel;
    }
}



/**
 * 支付服務類
 *
 * @author gp6
 * @date 2020/3/13
 */
public class PayService {

    public static void main(String[] args) {
        // 前臺傳的渠道 1:阿里 2:微信 3:Ping++ 4:通聯
        Integer channel = 1;

        // 支付邏輯(第一個方法)
        Context context = new Context(PayEnum.matchChannel(channel));
        context.singleQuota();
        context.pay();

        // 查詢接口(第二個方法)
        Context context2 = new Context(PayEnum.matchChannel(channel));
        context2.getResult();
    }
}

此時,如果想添加通聯的渠道只需在PayEnum中添加

OPERATION_MUL("com.gp6.pay.AllinpayHandler", 4)

然後在com.gp6.pay.AllinpayHandler中撰寫相應邏輯即可

Skr郎寫到此處,心滿意足的點點頭,心中評價道:這段代碼,邏輯嚴謹,註釋清晰,優雅中透露着灑脫,灑脫中透露着不羈,真是"此碼只應天上有,人間難得幾回尋!"
呀! 想不到我作詩的功夫也如此深厚,哎,如此完美的男人,別人只能羨慕嫉妒恨!

5 策略模式類圖

策略模式

StrategyContext相當於文章中的Context
StrategyOperation相當於文章中的PayHandle
StrategyOperationAdd與StrategyOperationMul相當於文章中的AlibabaHandler/AllinpayHandler/PingHandler/WechatHandler

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章