Java设计模式:适配者模式 - 结合框架源码 真实业务场景实践

302 阅读7分钟

适配者模式(Adapter Pattern)是一种常见的结构型设计模式,它允许接口不兼容的对象能够协同工作。

什么是适配者模式?

引用官方专业话术:将一个类的接口转换成客户希望的另外一个接口的设计模式,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

  • 目标接口(Target):客户期待的接口,系统需要的接口。
  • 适配者类(Adaptee):需要被适配的类。
  • 适配器类(Adapter):通过实现目标接口,并依赖适配者类,将适配者的接口转换为客户端期待的目标接口。

通过框架源码分析

源码实例1

使用过Springcloud Gateway网关的都知道其功能主要就是路由,其中过滤器就是路由的重要组成之一,过滤器分为局部过滤器 GatewayFilter(作用于指定的路由)和全局过滤器 GlobalFilter(作用于所有的路由),源码中整个过滤链中所有的过滤器都会适配成 GatewayFilter, GlobalFilter 会被 GatewayFilterAdapter适配器适配成 GatewayFilter,其中实现了Ordered 接口的 GlobalFilter 则会被 OrderedGatewayFilter 适配器适配成 GatewayFilter 主要源码如下:

// 在Springcloud Gateway源码 FilteringWebHandler 类中

private static List<GatewayFilter> loadFilters(List<GlobalFilter> filters) {
  return filters.stream().map(filter -> {
   // 全局过滤器适配成局部过滤器
   GatewayFilterAdapter gatewayFilter = new GatewayFilterAdapter(filter);
   // 实现了Ordered接口的全局过滤器也会适配成局部过滤器
   if (filter instanceof Ordered) {
    int order = ((Ordered) filter).getOrder();
    return new OrderedGatewayFilter(gatewayFilter, order);
   }
   return gatewayFilter;
  }).collect(Collectors.toList());
}

/**
 * ServerWebExchange: 
 * 保存了request、response、route 等请求相关交互的属性
 * 各过滤器和过滤链中可以获取相关属性
 */
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
  Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
  // 获取当期路由的局部过滤器
  List<GatewayFilter> gatewayFilters = route.getFilters();
  // 合并当期路由的局部过滤器和适配后的全局过滤器
  List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
  combined.addAll(gatewayFilters);
  // 过滤器进行排序
  AnnotationAwareOrderComparator.sort(combined);

  if (logger.isDebugEnabled()) {
   logger.debug("Sorted gatewayFilterFactories: " + combined);
  }
  // 执行过滤链
  return new DefaultGatewayFilterChain(combined).filter(exchange);
}
 
/**
 * 适配器:GatewayFilterAdapter
 * 适配者:GlobalFilter实现类
 * 目标接口:GatewayFilter接口
 */
private static class GatewayFilterAdapter implements GatewayFilter {
  // 适配者
  private final GlobalFilter delegate;

  GatewayFilterAdapter(GlobalFilter delegate) {
   this.delegate = delegate;
  }

  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
   // 执行适配者真正逻辑
   return this.delegate.filter(exchange, chain);
  }

  @Override
  public String toString() {
   final StringBuilder sb = new StringBuilder("GatewayFilterAdapter{");
   sb.append("delegate=").append(delegate);
   sb.append('}');
   return sb.toString();
  }

}

/**
 * 适配器:OrderedGatewayFilter
 * 适配者:GlobalFilter实现类
 * 目标接口:GatewayFilter接口
 */
public class OrderedGatewayFilter implements GatewayFilter, Ordered {

 private final GatewayFilter delegate;

 private final int order;

 public OrderedGatewayFilter(GatewayFilter delegate, int order) {
  this.delegate = delegate;
  this.order = order;
 }

 public GatewayFilter getDelegate() {
  return delegate;
 }

 @Override
 public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  return this.delegate.filter(exchange, chain);
 }

 @Override
 public int getOrder() {
  return this.order;
 }

 @Override
 public String toString() {
  return new StringBuilder("[").append(delegate).append(", order = ").append(order).append("]").toString();
 }

}

源码实例2

SpringMVC 源码中,DispatcherServlet 会对请求进行分发,其中Handler是对请求具体执行的处理器, SpringMVC中有四种类型 Handler 请求处理器(由不同 HandlerMapping 负责找出,不同处理器执行不同的方法

  1. 实现了 Controller 接口的Bean对象,执行的是Bean对象中的 handleRequest()
  2. 实现了 HttpRequestHandler 接口的Bean对象,执行的是Bean对象中的 handleRequest()
  3. 添加了 @RequestMapping 注解的方法,具体为一个 HandlerMethod,执行的就是当前加了注解的方法
  4. 一个 HandlerFunction 对象,执行的是 HandlerFunction 对象中的 handle()

SpringMVC中并不是采用 if else方式处理不同类型 Handler,而是采用适配者模式,把不同类型的handler适配成一个HandlerAdapter

  1. HttpRequestHandlerAdapter
  2. SimpleControllerHandlerAdapter
  3. RequestMappingHandlerAdapter
  4. HandlerFunctionAdapter

当请求过来的时候找到符合自己适配器再进行handler逻辑处理

// 在Spring MVC源码 DispatcherServlet 类中 
// HandlerAdapter实现类是适配器,handler是适配者,HandlerAdapter接口是目标接口
// 主要适配逻辑
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
  if (this.handlerAdapters != null) {
   for (HandlerAdapter adapter : this.handlerAdapters) {
    // 找到符合支持自己的适配器
    if (adapter.supports(handler)) {
     return adapter;
    }
   }
  }
  throw new ServletException("No adapter for handler [" + handler +
    "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

真实业务场景实践

接到这么一个需求:系统需要对美团名宿、高德云店、木鸟名宿等渠道进行直连,其中需要把系统的价格、库存等操作的信息推送给所有渠道,考虑到不同的直连渠道所调用的接口及具体实现逻辑各不相同,后期可能加入更多的直连渠道,同事考虑系统的扩展性和灵活性和耦合度,采取了该方案,包括以下几个部分:

  1. 消息生产者:负责生成不同的事件消息,如价格更新、库存更新等。
  2. 消息消费者:负责接收生产者产生的消息,并根据消息类型进行处理。
  3. 适配器:针对不同直连渠道,实现相应的接口和逻辑,以支持消息的生产和消费。
  4. 分发适配器:负责将消息分发给不同的适配器进行处理。

详细代码

/**
 * 直连分发器
 *
 * @author LGC
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class DispatcherDirect {

    /**
     * 所有适配器
     */
    private final List<DirectHandleAdapter> directHandleAdapters;

    /**
     * 直连库存handler
     * 针对不同直连进行了分发适配处理(适配者模式)
     *
     * @param objs
     * @param channel
     */
    public void handleStock(List<PushStockBO> objs, DirectChannelBO channel) {
        if (this.directHandleAdapters != null) {
            // 找到合适适配器并执行
            this.directHandleAdapters.stream()
                    .filter(i -> i.support(channel.getDirectEnum())).findFirst()
                    .ifPresent(adapter -> adapter.handleStock(objs, channel));
        }
    }
}

/**
 * 渠道信息
 *
 * @author LGC
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DirectChannelBO {

    /**
     * 渠道ID
     */
    private Integer channelId;


    /**
     * 租户ID tenantId
     */
    private Integer tenantId;


    /**
     * 直连 handler 映射枚举
     */
    private DirectEnum directEnum;

}

/**
 * 库存信息
 *
 * @author LGC
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PushStockBO {
    /**
     * 租户ID tenantId
     */
    private Integer tenantId;
    /**
     * 房型ID
     */
    private Integer roomTypeId;
    /**
     * 日期
     */
    private List<Date> dates;

}

/**
 * 直连是否适配支持
 * @author LGC
 */
public interface DirectSupporter {

    default boolean support(DirectEnum directEnum) {
        // 获取类注解信息
        DirectIn in = getClass().getAnnotation(DirectIn.class);
        if (in == null) {
            return false;
        }
        DirectEnum[] directEnums = in.value();
        for (DirectEnum t : directEnums) {
            // 传入的值是否匹配类注解
            if (t == directEnum) {
                return true;
            }
        }
        return false;
    }
}

/**
 * 包含直连枚举
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DirectIn {

    DirectEnum[] value();
}

/**
 * 直连映射枚举
 * 采用手动添加 直连渠道 和 handler(适配者)映射关系
 * 采用手动添加 直连渠道 和 配置类映射关系
 *
 * @author LGC
 */
@Getter
@AllArgsConstructor
public enum DirectEnum {
    /**
     * 直连映射枚举
     */
    MUNIAO(1, "木鸟名宿", MuniaoHandler.class, MuniaoPropties.class),
    MEITUAN(2, "美团名宿", MeituanHandler.class, MeituanProperties.class),
    ;

    /**
     * 唯一直连key
     */
    private final Integer key;
    /**
     * 唯一名称
     */
    private final String name;
    /**
     * handler 处理器类(适配者)
     */
    private final Class<?> handlerClazz;
    /**
     * 配置类,json 转换成不同配置类
     */
    private final Class<?> propertiesClazz;

    public static Class<?> getHandlerClazz(Integer key) {
        for (DirectEnum h : DirectEnum.values()) {
            if (h.getKey().equals(key)) {
                return h.getHandlerClazz();
            }
        }
        return null;
    }

    public static Class<?> getPropertiesClazz(Integer key) {
        for (DirectEnum h : DirectEnum.values()) {
            if (h.getKey().equals(key)) {
                return h.getPropertiesClazz();
            }
        }
        return null;
    }
}

/**
 * 美团名宿具体handler(适配者)
 *
 * @author LGC
 */
@Slf4j
@Component
public class MeituanHandler {

    public void handleStock(List<PushStockBO> objs, DirectChannelBO channel) {
        log.info("美团名宿库存推送");
    }
}

/**
 * 木鸟名宿handler(适配者)
 *
 * @author LGC
 */
@Slf4j
@Component
public class MuniaoHandler {

    public void handleStock(List<PushStockBO> objs, DirectChannelBO channel) {
        log.info("木名宿鸟库存推送");
    }
}

/**
 * DirectHandleAdapter目标接口
 * 继承 DirectSupporter 接口,用来判断是否适配该直连渠道(适配者)
 *
 * @author LGC
 */
public interface DirectHandleAdapter extends DirectSupporter {
    /**
     * 直连库存推送
     *
     * @param objs
     * @param channel
     */
    void handleStock(List<PushStockBO> objs, DirectChannelBO channel);
}

/**
 * 木鸟名宿名宿适配器
 *
 * @author LGC
 */
@Slf4j
@Component
@DirectIn(DirectEnum.MUNIAO)
@RequiredArgsConstructor
public class MuniaoDirectHandleAdapter implements DirectHandleAdapter {

    private final ApplicationContext applicationContext;

    @Override
    public void handleStock(List<PushStockBO> objs, DirectChannelBO channel) {
        log.info("映射找到具体handler适配者执行");
        Class<?> clazz = DirectEnum.MUNIAO.getClazz();
        MuniaoHandler handler = (MuniaoHandler) applicationContext.getBean(clazz);
        handler.handleStock(objs, channel);
    }

}

/**
 * 美团名宿适配器
 *
 * @author LGC
 */
@Slf4j
@Component
@DirectIn(DirectEnum.MEITUAN)
@RequiredArgsConstructor
public class MeituanDirectHandleAdapter implements DirectHandleAdapter {

    private final ApplicationContext applicationContext;

    @Override
    public void handleStock(List<PushStockBO> objs, DirectChannelBO channel) {
        log.info("映射找到具体handler适配者执行");
        Class<?> clazz = DirectEnum.MEITUAN.getClazz();
        MeituanHandler handler = (MeituanHandler) applicationContext.getBean(clazz);
        handler.handleStock(objs, channel);
    }

}

/**
 * @author LGC
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class AdapterPatternTest {

    @Autowired
    private DispatcherDirect dispatcherDirect;

    /**
     * 测试直连分发
     * 假设监听到库存事件
     * 1.获取所有直连渠道
     * 2.直连分发库存事件
     */
    @Test
    public void test() {
        // 接收的事件信息
        List<PushStockBO> pushStocks = new ArrayList<>();

        // 所有的直连渠道
        List<DirectChannelBO> channels = new ArrayList<>();
        // 木鸟名宿直连
        channels.add(new DirectChannelBO(1, 1, DirectEnum.MUNIAO));
        // 美团名宿直连
        channels.add(new DirectChannelBO(2, 1, DirectEnum.MEITUAN));


        // 分发事件
        for (DirectChannelBO channel : channels) {
            dispatcherDirect.handleStock(pushStocks, channel);
        }
    }
}

通过这种方式,系统可以灵活地添加或删除直连渠道,而无需修改其他部分的代码。同时,也减少了系统之间的耦合度,提高了系统的扩展性和灵活性。

该方案不知道是否能为后期系统兼容扩展得到很好表现,各位大佬有更好的方案,请多多指教,欢迎大家的留言

结语

合理使用适配者模式将带来以下好处:

  1. 将目标类和适配者类解耦
  2. 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性
  3. 灵活性和扩展性都非常好,符合开闭原则