• 基于接口编程:使用收集器模式使数据获取流程更加清晰可配置


    背景###

    订单导出中,常常需要从多个数据源获取数据,并组装数据详情。常规写法是这样的:

    public List<T> produceOrderDetails(List<String> keys, Context context){
    
       // STEP1
       List<OrderInfo> orderInfoList = obtainFromDetail(keys, context);
       List<OrderInfo> oldOrderInfoList= obtainFromHBase(keys, context);
       orderInfoList = merge(orderInfoList, oldOrderInfoList);
    
       // STEP2
       List<OrderItemInfo> orderItemInfoList = obtainFromDetail(keys, context);
       List<OrderItemInfo> oldOrderItemInfoList= obtainFromHBase(keys, context);
       orderItemInfoList = merge(orderItemInfoList, oldOrderItemInfoList);
    
      // STEP3
      orderInfoList = obtainAllOrderLevelInfo(keys, context, orderInfoList);
     
      // STEP4
      orderItemInfoList = obtainAllOrderItemLevelInfo(keys, context, orderItemInfoList);
    
      // STEP5:
      // other codes ...
    }
    
    

    看上去是不是有些混乱?

    本文将应用“基于接口编程”的思想,改造这个流程,使之更清晰,可配置化。

    基于接口编程###

    基于接口编程,提倡不直接编写具体实现,而是先定义接口及交互关系, 然后编写接口的多个组件实现,最后,通过组件编排将实现串联起来。

    确立数据模型####

    首要的是确立数据模型。 数据详情获取的整个流程,都会围绕这个数据模型而展开。

    在这个例子中, 可以看到, 主要的数据对象是 List<OrderInfo>, List<OrderItemInfo> , 分别对应订单级别和商品级别的信息。在获取数据的过程中,将源源不断的新数据详情充填这两个对象。

    @Data
    public class OrderInfo {
    
      private String orderNo;
    
      public OrderInfo(String orderNo) {
        this.orderNo = orderNo;
      }
    }
    
    @Data
    public class OrderItemInfo {
    
      private String orderNo;
      private String itemId;
    
      private String expressId;
    
      public OrderItemInfo(String orderNo, String itemId) {
        this.orderNo = orderNo;
        this.itemId = itemId;
      }
    }
    

    定义接口####

    基于数据模型,定义数据收集的接口。

    public interface OrderDetailCollector {
      void collect(List<OrderInfo> orderInfoList, List<OrderItemInfo> orderItemInfoList);
    }
    

    组件实现####

    编写组件类,使用适配器,将现有获取数据的实现迁入。 组件化是配置化的前提。

    @Component("baseOrderDetailCollector")
    public class BaseOrderDetailCollector implements OrderDetailCollector {
    
      @Override
      public void collect(List<OrderInfo> orderInfoList, List<OrderItemInfo> orderItemInfoList) {
        // 这里可以使用适配器
        orderInfoList.addAll(Arrays.asList(new OrderInfo("E001"), new OrderInfo("E002")));
        orderItemInfoList.addAll(Arrays.asList(new OrderItemInfo("E001", "I00001"), new OrderItemInfo("E002", "I000002")));
      }
    }
    
    @Component("expressOrderDetailCollector")
    public class ExpressOrderDetailCollector implements OrderDetailCollector {
    
      @Override
      public void collect(List<OrderInfo> orderInfoList, List<OrderItemInfo> orderItemInfoList) {
        orderItemInfoList.forEach(
            orderItemInfo ->  orderItemInfo.setExpressId("EXP")
        );
      }
    }
    

    组件工厂####

    在实现一系列组件后,需要创建一个组件工厂,让客户端方便地获取组件实现类。 通常会用到 ApplicationContextAware 这个接口。

    @Component("orderDetailCollectorFactory")
    public class OrderDetailCollectorFactory implements ApplicationContextAware {
    
      private static Logger logger = LoggerFactory.getLogger(OrderDetailCollectorFactory.class);
    
      private ApplicationContext applicationContext;
    
      private Map<String, OrderDetailCollector> orderDetailCollectorMap;
    
      private static boolean hasInitialized = false;
    
      @PostConstruct
      public void init() {
        try {
          if(!hasInitialized){
            synchronized (OrderDetailCollectorFactory.class){
              if(!hasInitialized) {
                orderDetailCollectorMap = applicationContext.getBeansOfType(OrderDetailCollector.class);
                logger.info("detailCollectorMap: {}", orderDetailCollectorMap);
              }
            }
          }
        } catch (Exception ex) {
          logger.error("failed to load order detail collector !");
          throw new RuntimeException(ServerError.getMessage());
        }
    
      }
    
      public OrderDetailCollector get(String name) {
        return orderDetailCollectorMap.get(name);
      }
    
      @Override
      public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
      }
    }
    

    客户端使用####

    通常,在具有一系列组件实现后,客户端就可以通过配置的方式来灵活选取和编排组件,实现灵活多变的功能。

    public class CollectorClient {
    
      @Resource
      OrderDetailCollectorFactory orderDetailCollectorFactory;
    
      public void usage() {
    
        // 可以配置在 DB 或 Apollo 里
        List<String> collectors = Arrays.asList("baseOrderDetailCollector", "expressOrderDetailCollector");
    
        List<OrderInfo> orderInfos = new ArrayList<>();
        List<OrderItemInfo> orderItemInfos = new ArrayList<>();
    
        collectors.forEach(
            collector -> orderDetailCollectorFactory.get(collector).collect(orderInfos, orderItemInfos)
        );
    
      }
    }
    

    单例模式###

    上文中的 Collector 组件及工厂 OrderDetailCollectorFactory 都应用了单例模式。 如何实现单例模式呢 ?

    DCL####

    注意下面的代码使用了 DCL (双重检测锁定)。

    if(!hasInitialized){
            synchronized (OrderDetailCollectorFactory.class){
              if(!hasInitialized) {
                orderDetailCollectorMap = applicationContext.getBeansOfType(OrderDetailCollector.class);
                logger.info("detailCollectorMap: {}", orderDetailCollectorMap);
              }
            }
          }
    

    不过这样写是不严谨的。

    • 如果是一个线程,只执行一次,那么并不需要写成这么复杂,不需要使用 if (!hasInitialized) 和 synchronized 。
    • 如果是多线程,由于 hasInitialized 并没有被置为 true ,因此,每个进入的线程都会执行一遍这里的逻辑。
    • 即使 hasInitialized 设置为 true , 由于不一定被其他线程看到,因此,每个进入的线程依然会执行一遍这里的逻辑。

    要更加严谨一些,应该写成如下形式:

    private volatile static boolean hasInitialized = false;
    
    if(!hasInitialized){
            synchronized (OrderDetailCollectorFactory.class){
              if(!hasInitialized) {
                orderDetailCollectorMap = applicationContext.getBeansOfType(OrderDetailCollector.class);
                logger.info("detailCollectorMap: {}", orderDetailCollectorMap);
                hasInitialized = true;
              }
            }
          }
    

    volatile 含“禁止指令重排序”的语义,保证 hasInitialized 修改后对其他线程可见 。当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。


    Spring单例####

    @Component 是 Spring bean 组件的注解,默认会创建成单例。 那么 Spring 是如何实现单例模式的呢 ?

    Spring 3.2.13 版本中,类 DefaultSingletonBeanRegistry 是 Spring 用来管理 singleton 实例的注册表,实现单例的注册、获取和销毁。 单例通过 getSingleton 方法获取。要点如下:

    • 数据结构:使用 ConcurrentHashMap singletonObjects 来存储单例名称及实例的映射关系; 使用 Map earlySingletonObjects 作为单例的缓存;使用 LinkedHashSet registeredSingletons 存储已经注册的单例名称及注册顺序;使用 ConcurrentHashMap singletonsCurrentlyInCreation 存储单例是否正在创建中;使用 singletonFactories 存储 bean 名称及 Singleton 工厂对象的映射关系;使用 inCreationCheckExclusions 存储不需要创建时不需要检查的 bean 名称;

    • 单例获取: 首先从 singletonObjects 获取指定 bean 名称的对象; 如果没有获取到对象,并且当前对象在创建中,那么从 earlySingletonObjects 中获取;如果 earlySingletonObjects 中也没有,那么从 singletonFactories 的 singleton 工厂中去制造和获取单例对象。获取到指定单例对象后,会进行单例的注册工作,对以上数据结构进行添加和删除。

    单例的创建入口是类 AbstractBeanFactory 的 doGetBean 方法。要点如下:

    • 最底层的创建是通过反射调用构造器来实现的;这里有一些权限的判断和封装;

    • 使用 CglibSubclassingInstantiationStrategy 策略来初始化 Bean 。

    • 在 bean 创建前后,有一些钩子方法,比如 是否使用代理 ( AbstractAutoProxyCreator.createProxy 方法 ),创建前与创建后的检测; 创建前和创建后的初始化等。


    小结###

    从上面例子可见,确立数据模型,定义接口,实现组件,进行组件编排,是使得代码设计与实现更加清晰灵活的常用模式。

    在实现功能服务或业务流程时,不是想到哪写到哪,而是首先进行一番设计和构思,想清楚数据、接口、组件、交互,然后再进行编程,往往实现出来会更加清晰、灵活、可配置化。

    业务就是配置。


    参考资料###

  • 相关阅读:
    深入JavaScript之获取cookie以及删除cookie
    js 首次进入弹窗
    jquery 点击加载更多
    express 设置允许跨域访问
    微信小程序之全局储存
    jquery 在页面上根据ID定位(jQuery锚点跳转及相关操作)
    我也想聊聊 OAuth 2.0 —— 基本概念
    一行代码,发送邮件
    【Git使用】强制推送代码到多个远程仓库
    一秒钟生成自己的iOS客户端
  • 原文地址:https://www.cnblogs.com/lovesqcc/p/10692213.html
Copyright © 2020-2023  润新知