架构之基于接口编程

简介

接口越抽象、越顶层、越脱离某一种实现的设计,越能提高代码的灵活性、越能应对为了需求的变化,好的代码设计,不仅能够应对当前的需求,而且在将来需求发生变化的时候,任然能够在不破坏原有设计的情况下灵活应对。抽象是提高代码的扩展性、灵活性、可维护性的有效手段。我们先来看一段代码:

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 发布!

发布了75 篇原创文章 · 获赞 85 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/zhangchangbin123/article/details/103299291