架構之基於接口編程

簡介

接口越抽象、越頂層、越脫離某一種實現的設計,越能提高代碼的靈活性、越能應對爲了需求的變化,好的代碼設計,不僅能夠應對當前的需求,而且在將來需求發生變化的時候,任然能夠在不破壞原有設計的情況下靈活應對。抽象是提高代碼的擴展性、靈活性、可維護性的有效手段。我們先來看一段代碼:

public class AliPayChannel {

    /**
     *支持寶支付
     * @param orderId
     * @return
     */
    public boolean alipay(String orderId){
        //具體代碼省略
        return true;
    }

    /**
     * 支付寶退款
     * @param orderId
     * @return
     */
    public boolean aliRefund(String orderId){
        //具體代碼省略
        return true;
    }

    /**
     * 支付寶創建訂單
     * @param orderId
     * @return
     */
    public boolean aliOrderCreate(String orderId){
        //具體代碼省略
        return true;
    }

}

這一段代碼主要是模擬支付的三個接口alipay支付接口、aliRefund退款接口、創建訂單接口;代碼看起來很簡單,一看也沒什麼問題,也能夠滿足需求;但是在後期由於業務的擴展需要接入其它的支付渠道,那我們是在寫一套一樣的流程還是在原來的支付接口上面改呢?其實這個設計就是有問題的,主要有幾個方面:

  • 方法的命名alipay、aliRefund、aliOrderCreate就限制了後期的擴展,如果在擴展一個支付渠道完全用不上,函數的命名不能暴露細節,要更加抽象的方式比如pay、refund、creatOrder
  • 封裝具體的實現細節,跟支付寶的具體細節不應該暴露給調用方
  • 爲實現類定義抽象接口,具體的實現類統一依賴統一的接口定義
    按照以上的思路,重構了以下代碼

代碼重構

接口定義

定義支付渠道的接口,定義三個抽象方法,分別爲支付、退款、創建訂單

public interface PayChannel {

    boolean pay(String orderId);

    boolean refund(String orderId);

    boolean createOrder(String orderId);

}

支付寶支付渠道

新增支付寶渠道並實現三個具體的細節

public class AlipayChannelImpl implements PayChannel{

    /**
     * 支付寶支付
     * @param orderId
     * @return
     */
    public boolean pay(String orderId) {
        System.out.println("支付寶支付.訂單號爲:" orderId);
        return false;
    }

    /**
     * 支付寶退款
     * @param orderId
     * @return
     */
    public boolean refund(String orderId) {
        System.out.println("支付寶退款.訂單號爲:" orderId);
        return false;
    }

    /**
     * 支付寶訂單創建
     * @param orderId
     * @return
     */
    public boolean createOrder(String orderId) {
        System.out.println("支付寶創建訂單.訂單號爲:" orderId);
        return false;
    }

}

微信支付渠道

新增微信渠道並實現三個具體的細節

public class WeChatChannelImpl implements PayChannel {

    /**
     * 微信支付
     * @param orderId
     * @return
     */
    public boolean pay(String orderId) {
        System.out.println("微信支付.訂單號爲:" orderId);
        return false;
    }

    /**
     * 微信退款
     * @param orderId
     * @return
     */
    public boolean refund(String orderId) {
        System.out.println("微信退款.訂單號爲:" orderId);
        return false;
    }

    /**
     * 微信訂單創建
     * @param orderId
     * @return
     */
    public boolean createOrder(String orderId) {
        System.out.println("微信創建訂單.訂單號爲:" orderId);
        return false;
    }
}

基於接口調用

基於接口編程模式不需要關注具體的實現類的細節,我們只要知道接口類和抽象方法,具體的實現類我們可以通過spi機制或者簡單工廠的方式進行調用,達到代碼的一個低耦合的,可擴展的能力,無論是可讀性還是可維護性都比較好,下面詳細的介紹下這兩種方式:

SPI機制調用

基於spi機制調用,我們這裏需要藉助一個自定義的註解,通過這個註解標識具體的支付渠道的實現類,進行調用

註解定義

該註解有一個默認值,爲支付類型

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
public @interface Tag {
    String defaultId() default "";
}

實現類標註解

@Tag標識支付渠道類型支付寶渠道

@Tag(defaultId = "alipay")
public class AlipayChannelImpl implements PayChannel
具體代碼省略

微信渠道

@Tag(defaultId = "wechat")
public class WeChatChannelImpl implements PayChannel
具體代碼省略

spi機制封裝

主要通過接口類型,和實現類的標註去獲取具體的實現類,調用者不用關係具體的實現

public class SpringFactoriesLoaderUtil {

    public static <T> T loadFactories(Class<T> type,String tag){
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Set<String> names = new LinkedHashSet<String>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        for (String name : names) {
            try {
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                Tag annotation = (Tag) instanceClass.getAnnotation(Tag.class);
                if (tag.equals(annotation.defaultId())){
                    return (T) ReflectionUtils.accessibleConstructor(instanceClass, new Class[0]).newInstance(new Object[0]);
                }
            } catch (Exception e ) {
                e.printStackTrace();
            }

        }
        return null;
    }

}

spi配置

我們在resources下面新建META-INF/spring.factories文件,內容如下,主要配置具體的支付渠道

cn.cbzcb.pay.PayChannel=\
cn.cbzcb.pay.AlipayChannelImpl,\
cn.cbzcb.pay.WeChatChannelImpl

調用

我們在這裏調用的時候,只要關係具體的支付渠道的類型就可以了,然後通過抽象方法調用

public class PaymentScenario {
    public static void spiScenario(String orderId, String way){
        PayChannel payChannel = SpringFactoriesLoaderUtil.loadFactories(PayChannel.class,way);
        payChannel.createOrder(orderId);
        payChannel.pay(orderId);
        payChannel.refund(orderId);
    }
    

    public static void main(String [] args){
        System.out.println("*************************基於spi實現************************************");
        //支付寶支付
        String orderId = "123";
        String way = "alipay";
        spiScenario(orderId,way);
        //微信支付
        way = "wechat";
        spiScenario(orderId,way);
    }
}

基於簡單工廠進行調用

定義配置類

我們這裏定義一個單例,用於模擬真實項目的配置文件,其功能類似於spi的配置文件

public class PayChannelConfig {
    private static PayChannelConfig instance = new PayChannelConfig();
    private PayChannelConfig(){};
    private  Map<String,String> config = new HashMap<String, String>();
    public static PayChannelConfig getInstance(){
        return instance;
    }

    public Map<String, String> getConfig() {
        config.put("alipay","cn.cbzcb.pay.AlipayChannelImpl");
        config.put("wechat","cn.cbzcb.pay.WeChatChannelImpl");
        return config;
    }

    public void setConfig(Map<String, String> config) {
        this.config = config;
    }
}

定義工廠類

我們也只要關係接口類型和具體的支付渠道,通過反射機制調用

public class PayChannelFactory {
    public static <T> T newInstance(Class<T> type, String way){
        Map<String,String> config = PayChannelConfig.getInstance().getConfig();
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        String name = config.get(way);
        PayChannel payChannel = null;
        Class<?> instanceClass = null;
        try {
            instanceClass = ClassUtils.forName(name, classLoader);
            return (T) ReflectionUtils.accessibleConstructor(instanceClass, new Class[0]).newInstance(new Object[0]);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

調用

也只要關注接口類型和調用實現類的標識

public class PaymentScenario {
   
    public static void factoryScenario(String orderId, String way){
        PayChannel payChannel = PayChannelFactory.newInstance(PayChannel.class,way);
        payChannel.createOrder(orderId);
        payChannel.pay(orderId);
        payChannel.refund(orderId);

    }

    public static void main(String [] args){

        System.out.println("*************************基於簡單工廠實現************************************");
        String orderId = "456";
        String way = "alipay";
        factoryScenario(orderId,way);
        way = "wechat";
        factoryScenario(orderId,way);
    }
}

調用示例

在進行架構設計的時候要多考慮設計原則和設計模型這樣對項目以後的擴展、靈活性、可維護性都是有好處的.

代碼:https://github.com/itrickzhang/Inteface-program-demo

個人網站:http://www.cnzcb.cn

本文由博客一文多發平臺 OpenWrite 發佈!

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