适配者模式(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 负责找出,不同处理器执行不同的方法
- 实现了 Controller 接口的Bean对象,执行的是Bean对象中的 handleRequest()
- 实现了 HttpRequestHandler 接口的Bean对象,执行的是Bean对象中的 handleRequest()
- 添加了 @RequestMapping 注解的方法,具体为一个 HandlerMethod,执行的就是当前加了注解的方法
- 一个 HandlerFunction 对象,执行的是 HandlerFunction 对象中的 handle()
SpringMVC中并不是采用 if else方式处理不同类型 Handler,而是采用适配者模式,把不同类型的handler适配成一个HandlerAdapter
- HttpRequestHandlerAdapter
- SimpleControllerHandlerAdapter
- RequestMappingHandlerAdapter
- 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");
}
真实业务场景实践
接到这么一个需求:系统需要对美团名宿、高德云店、木鸟名宿等渠道进行直连,其中需要把系统的价格、库存等操作的信息推送给所有渠道,考虑到不同的直连渠道所调用的接口及具体实现逻辑各不相同,后期可能加入更多的直连渠道,同事考虑系统的扩展性和灵活性和耦合度,采取了该方案,包括以下几个部分:
- 消息生产者:负责生成不同的事件消息,如价格更新、库存更新等。
- 消息消费者:负责接收生产者产生的消息,并根据消息类型进行处理。
- 适配器:针对不同直连渠道,实现相应的接口和逻辑,以支持消息的生产和消费。
- 分发适配器:负责将消息分发给不同的适配器进行处理。
详细代码
/**
* 直连分发器
*
* @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);
}
}
}
通过这种方式,系统可以灵活地添加或删除直连渠道,而无需修改其他部分的代码。同时,也减少了系统之间的耦合度,提高了系统的扩展性和灵活性。
该方案不知道是否能为后期系统兼容扩展得到很好表现,各位大佬有更好的方案,请多多指教,欢迎大家的留言
结语
合理使用适配者模式将带来以下好处:
- 将目标类和适配者类解耦
- 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性
- 灵活性和扩展性都非常好,符合开闭原则