重學 Java 設計模式:實戰橋接模式(多支付渠道「微信、支付寶」與多支付模式「刷臉、指紋」場景)

作者:小傅哥
博客:https://bugstack.cn – 編寫系列原創專題文章

沉澱、分享、成長,讓自己和他人都能有所收穫!

一、前言

為什麼你的代碼那麼多ifelse

同類的業務、同樣的功能,怎麼就你能寫出來那麼多ifelse。很多時候一些剛剛從校園進入企業的萌新,或者一部分從小公司跳槽到大企業的程序員,初次承接業務需求的時候,往往編碼還不成熟,經常一桿到底的寫需求。初次實現確實很快,但是後期維護和擴展就十分痛苦。因為一段代碼的可讀性閱讀他後期的維護成本也就越高。

設計模式是可以幫助你改善代碼

很多時候你寫出來的ifelse都是沒有考慮使用設計模式優化,就像;同類服務的不同接口適配包裝、同類物料不同組合的建造、多種獎品組合的營銷工廠等等。它們都可以讓你代碼中原本使用if判斷的地方,變成一組組類和面向對象的實現過程。

怎麼把設計模式和實際開發結合起來

多從實際場景思考,只找到代碼優化的最佳點,不要可以想着設計模式的使用。就像你最開始看設計模式適合,因為沒有真實的場景模擬案例,都是一些畫圓形、方形,對新人或者理解能力還不到的夥伴來說很不友好。所以即使學了半天 ,但實際使用還是摸不着頭腦。

二、開發環境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程三個,可以通過關注公眾號bugstack蟲洞棧,回復源碼下載獲取(打開獲取的鏈接,找到序號18)
工程 描述
itstack-demo-design-7-01 使用一坨代碼實現業務需求
itstack-demo-design-7-02 通過設計模式優化改造代碼,產生對比性從而學習

三、橋接模式介紹

橋接模式的主要作用就是通過將抽象部分與實現部分分離,把多種可匹配的使用進行組合。說白了核心實現也就是在A類中含有B類接口,通過構造函數傳遞B類的實現,這個B類就是設計的

那麼這樣的橋接模式,在我們平常的開發中有哪些場景

JDBC多種驅動程序的實現、同品牌類型的台式機和筆記本平板、業務實現中的多類接口同組過濾服務等。這些場景都比較適合使用橋接模式進行實現,因為在一些組合中如果有如果每一個類都實現不同的服務可能會出現笛卡爾積,而使用橋接模式就可以非常簡單。

四、案例場景模擬

隨着市場的競爭在支付服務行業出現了微信和支付寶還包括一些其他支付服務,但是對於商家來說並不希望改變用戶習慣。就像如果我的地攤只能使用微信或者只能使用支付寶付款,那麼就會讓我顧客傷心,雞蛋灌餅也賣不動了。

在這個時候就出現了第三方平台,把市面上綜合佔據市場90%以上的支付服務都集中到自己平台中,再把這樣的平台提供給店鋪、超市、地攤使用,同時支持人臉、掃描、密碼多種方式。

我們這個案例就模擬一個這樣的第三方平台來承接各個支付能力,同時使用自家的人臉讓用戶支付起來更加容易。那麼這裏就出現了多支付多模式的融合使用,如果給每一個支付都實現一次不同的模式,即使是繼承類也需要開發好多。而且隨着後面接入了更多的支付服務或者支付方式,就會呈爆炸似的擴展。

所以你現在可以思考一下這樣的場景該如何實現?

五、用一坨坨代碼實現

產品經理說老闆要的需求,要儘快上,kpi你看着弄!

既然你逼我那就別怪我無情,還沒有我一個類寫不完的需求!反正寫完就完事了,拿完績效也要走了天天逼着寫需求,代碼越來越亂心疼後面的兄弟3秒。

1. 工程結構

itstack-demo-design-7-01
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                └── PayController.java
  • 只有一個類裏面都是ifelse,這個類實現了支付和模式的全部功能。

2. 代碼實現

public class PayController {

    private Logger logger = LoggerFactory.getLogger(PayController.class);

    public boolean doPay(String uId, String tradeId, BigDecimal amount, int channelType, int modeType) {
        // 微信支付
        if (1 == channelType) {
            logger.info("模擬微信渠道支付划賬開始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
            if (1 == modeType) {
                logger.info("密碼支付,風控校驗環境安全");
            } else if (2 == modeType) {
                logger.info("人臉支付,風控校驗臉部識別");
            } else if (3 == modeType) {
                logger.info("指紋支付,風控校驗指紋信息");
            }
        }
        // 支付寶支付
        else if (2 == channelType) {
            logger.info("模擬支付寶渠道支付划賬開始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
            if (1 == modeType) {
                logger.info("密碼支付,風控校驗環境安全");
            } else if (2 == modeType) {
                logger.info("人臉支付,風控校驗臉部識別");
            } else if (3 == modeType) {
                logger.info("指紋支付,風控校驗指紋信息");
            }
        }
        return true;
    }

}
  • 上面的類提供了一個支付服務功能,通過提供的必要字段;用戶ID交易ID金額渠道模式,來控制支付方式。
  • 以上的ifelse應該是最差的一種寫法,即使寫ifelse也是可以優化的方式去寫的。

3. 測試驗證

3.1 編寫測試類

@Test
public void test_pay() {
    PayController pay = new PayController();
    System.out.println("\r\n模擬測試場景;微信支付、人臉方式。");
    pay.doPay("weixin_1092033111", "100000109893", new BigDecimal(100), 1, 2);
    
    System.out.println("\r\n模擬測試場景;支付寶支付、指紋方式。");
    pay.doPay("jlu19dlxo111","100000109894",new BigDecimal(100), 2, 3);
}
  • 以上分別測試了兩種不同的支付類型和支付模式;微信人臉支付、支付寶指紋支付

3.2 測試結果

模擬測試場景;微信支付、人臉方式。
23:05:59.152 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬微信渠道支付划賬開始。uId:weixin_1092033111 tradeId:100000109893 amount:100
23:05:59.155 [main] INFO  o.i.demo.design.pay.mode.PayCypher - 人臉支付,風控校驗臉部識別
23:05:59.155 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬微信渠道支付風控校驗。uId:weixin_1092033111 tradeId:100000109893 security:true
23:05:59.155 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬微信渠道支付划賬成功。uId:weixin_1092033111 tradeId:100000109893 amount:100

模擬測試場景;支付寶支付、指紋方式。
23:05:59.156 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬支付寶渠道支付划賬開始。uId:jlu19dlxo111 tradeId:100000109894 amount:100
23:05:59.156 [main] INFO  o.i.demo.design.pay.mode.PayCypher - 指紋支付,風控校驗指紋信息
23:05:59.156 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬支付寶渠道支付風控校驗。uId:jlu19dlxo111 tradeId:100000109894 security:true
23:05:59.156 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬支付寶渠道支付划賬成功。uId:jlu19dlxo111 tradeId:100000109894 amount:100

Process finished with exit code 0
  • 從測試結果看已經滿足了我們的不同支付類型和支付模式的組合,但是這樣的代碼在後面的維護以及擴展都會變得非常複雜。

六、橋接模式重構代碼

接下來使用橋接模式來進行代碼優化,也算是一次很小的重構。

從上面的ifelse方式實現來看,這是兩種不同類型的相互組合。那麼就可以把支付方式支付模式進行分離通過抽象類依賴實現類的方式進行橋接,通過這樣的拆分后支付與模式其實是可以單獨使用的,當需要組合時候只需要把模式傳遞給支付即可。

橋接模式的關鍵是選擇的橋接點拆分,是否可以找到這樣類似的相互組合,如果沒有就不必要非得使用橋接模式。

1. 工程結構

itstack-demo-design-7-02
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design.pay
    │           ├── channel
    │           │   ├── Pay.java
    │           │   ├── WxPay.java
    │           │   └── ZfbPay.java
    │           └── mode
    │               ├── IPayMode.java
    │               ├── PayCypher.java
    │               ├── PayFaceMode.java
    │               └── PayFingerprintMode.java
    └── test
         └── java
             └── org.itstack.demo.design.test
                 └── ApiTest.java

橋接模式模型結構

  • 左側Pay是一個抽象類,往下是它的兩個支付類型實現;微信支付、支付寶支付。
  • 右側IPayMode是一個接口,往下是它的兩個支付模型;刷臉支付、指紋支付。
  • 那麼,支付類型 × 支付模型 = 就可以得到相應的組合。

2. 代碼實現

2.1 支付類型橋接抽象類

public abstract class Pay {

    protected Logger logger = LoggerFactory.getLogger(Pay.class);

    protected IPayMode payMode;

    public Pay(IPayMode payMode) {
        this.payMode = payMode;
    }

    public abstract String transfer(String uId, String tradeId, BigDecimal amount);

}
  • 在這個類中定義了支付方式的需要實現的划賬接口:transfer,以及橋接接口;IPayMode,並在構造函數中用戶方自行選擇支付方式。
  • 如果沒有接觸過此類實現,可以重點關注 IPayMode payMode,這部分是橋接的核心。

2.2 兩個支付類型的實現

微信支付

public class WxPay extends Pay {

    public WxPay(IPayMode payMode) {
        super(payMode);
    }

    public String transfer(String uId, String tradeId, BigDecimal amount) {
        logger.info("模擬微信渠道支付划賬開始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
        boolean security = payMode.security(uId);
        logger.info("模擬微信渠道支付風控校驗。uId:{} tradeId:{} security:{}", uId, tradeId, security);
        if (!security) {
            logger.info("模擬微信渠道支付划賬攔截。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
            return "0001";
        }
        logger.info("模擬微信渠道支付划賬成功。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
        return "0000";
    }

}

支付寶支付

public class ZfbPay extends Pay {

    public ZfbPay(IPayMode payMode) {
        super(payMode);
    }

    public String transfer(String uId, String tradeId, BigDecimal amount) {
        logger.info("模擬支付寶渠道支付划賬開始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
        boolean security = payMode.security(uId);
        logger.info("模擬支付寶渠道支付風控校驗。uId:{} tradeId:{} security:{}", uId, tradeId, security);
        if (!security) {
            logger.info("模擬支付寶渠道支付划賬攔截。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
            return "0001";
        }
        logger.info("模擬支付寶渠道支付划賬成功。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
        return "0000";
    }

}
  • 這裏分別模擬了調用第三方的兩個支付渠道;微信、支付寶,當然作為支付綜合平台可能不只是接了這兩個渠道,還會有其很跟多渠道。
  • 另外可以看到在支付的時候分別都調用了風控的接口進行驗證,也就是不同模式的支付(刷臉指紋),都需要過指定的風控,才能保證支付安全。

2.3 定義支付模式接口

public interface IPayMode {

    boolean security(String uId);

}
  • 任何一個支付模式;刷臉、指紋、密碼,都會過不同程度的安全風控,這裏定義一個安全校驗接口。

2.4 三種支付模式風控(刷臉、指紋、密碼)

刷臉

public class PayFaceMode implements IPayMode{

    protected Logger logger = LoggerFactory.getLogger(PayCypher.class);

    public boolean security(String uId) {
        logger.info("人臉支付,風控校驗臉部識別");
        return true;
    }

}

指紋

public class PayFingerprintMode implements IPayMode{

    protected Logger logger = LoggerFactory.getLogger(PayCypher.class);

    public boolean security(String uId) {
        logger.info("指紋支付,風控校驗指紋信息");
        return true;
    }

}

密碼

public class PayCypher implements IPayMode{

    protected Logger logger = LoggerFactory.getLogger(PayCypher.class);

    public boolean security(String uId) {
        logger.info("密碼支付,風控校驗環境安全");
        return true;
    }

}
  • 在這裏實現了三種支付模式(刷臉、指紋、密碼)的風控校驗,在用戶選擇不同支付類型的時候,則會進行相應的風控攔截以此保障支付安全。

3. 測試驗證

3.1 編寫測試類

@Test
public void test_pay() {
    System.out.println("\r\n模擬測試場景;微信支付、人臉方式。");
    Pay wxPay = new WxPay(new PayFaceMode());
    wxPay.transfer("weixin_1092033111", "100000109893", new BigDecimal(100));

    System.out.println("\r\n模擬測試場景;支付寶支付、指紋方式。");
    Pay zfbPay = new ZfbPay(new PayFingerprintMode());
    zfbPay.transfer("jlu19dlxo111","100000109894",new BigDecimal(100));
}
  • 與上面的ifelse實現方式相比,這裏的調用方式變得整潔、乾淨、易使用;new WxPay(new PayFaceMode())new ZfbPay(new PayFingerprintMode())
  • 外部的使用接口的用戶不需要關心具體的實現,只按需選擇使用即可。

3.2 測試結果

模擬測試場景;微信支付、人臉方式。
23:14:40.911 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬微信渠道支付划賬開始。uId:weixin_1092033111 tradeId:100000109893 amount:100
23:14:40.914 [main] INFO  o.i.demo.design.pay.mode.PayCypher - 人臉支付,風控校驗臉部識別
23:14:40.914 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬微信渠道支付風控校驗。uId:weixin_1092033111 tradeId:100000109893 security:true
23:14:40.915 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬微信渠道支付划賬成功。uId:weixin_1092033111 tradeId:100000109893 amount:100

模擬測試場景;支付寶支付、指紋方式。
23:14:40.915 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬支付寶渠道支付划賬開始。uId:jlu19dlxo111 tradeId:100000109894 amount:100
23:14:40.915 [main] INFO  o.i.demo.design.pay.mode.PayCypher - 指紋支付,風控校驗指紋信息
23:14:40.915 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬支付寶渠道支付風控校驗。uId:jlu19dlxo111 tradeId:100000109894 security:true
23:14:40.915 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬支付寶渠道支付划賬成功。uId:jlu19dlxo111 tradeId:100000109894 amount:100

Process finished with exit code 0
  • 從測試結果看內容是一樣的,但是整體的實現方式有了很大的變化。所以有時候不能只看結果,也要看看過程

七、總結

  • 通過模擬微信與支付寶兩個支付渠道在不同的支付模式下,刷臉指紋密碼,的組合從而體現了橋接模式的在這類場景中的合理運用。簡化了代碼的開發,給後續的需求迭代增加了很好的擴展性。
  • 從橋接模式的實現形式來看滿足了單一職責和開閉原則,讓每一部分內容都很清晰易於維護和拓展,但如果我們是實現的高內聚的代碼,那麼就會很複雜。所以在選擇重構代碼的時候,需要考慮好整體的設計,否則選不到合理的設計模式,將會讓代碼變得難以開發。
  • 任何一種設計模式的選擇和使用都應該遵頊符合場景為主,不要刻意使用。而且統一場景因為業務的複雜從而可能需要使用到多種設計模式的組合,才能將代碼設計的更加合理。但這種經驗需要從實際的項目中學習經驗,並提不斷的運用。

八、推薦閱讀

  • 1. 重學 Java 設計模式:實戰工廠方法模式(多種類型商品發獎場景)
  • 2. 重學 Java 設計模式:實戰抽象工廠模式(替換Redis雙集群升級場景)
  • 3. 重學 Java 設計模式:實戰建造者模式(裝修物料組合套餐選配場景)
  • 4. 重學 Java 設計模式:實戰原型模式(多套試每人題目和答案亂序場景)
  • 5. 重學 Java 設計模式:實戰單例模式(Effective Java 作者推薦枚舉單例模式)

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!