阅读提示:很多同学写代码看到
Map + Context路由就全部当成策略模式,踩了大量坑。本文一次性讲清三者边界:纯策略、纯适配器、适配器+策略组合,全部配套 Spring 实战代码、场景区分、踩坑总结。
前言
业务开发里,多分支场景是家常便饭:多渠道上报、多文件存储、多种AI对话处理器、多种单据解析。
大家为了消灭 if-else,很容易写出长得几乎一模一样的代码:统一接口 + 多个实现 + Spring Map自动注入 + Context调度。
但是这里藏着一个高频面试+代码评审大坑:
- ✅ 策略模式:实现类原生遵守统一契约,内部是不同算法
- ✅ 适配器模式:实现类包装异构第三方/老组件,做接口翻译转换
- ✅ 适配器+策略组合:先用适配器抹平异构SDK,产出统一接口;再用策略做可替换的路由选择规则
很多博客只单独讲其中一种,没有横向对比。本文三套代码一脉相承,业务域统一用【指标上报】,方便对照阅读。
一句话速记评审口诀:
看实现类内部:自己写算法 → 策略;包了别的异构对象做翻译 → 适配器;先翻译归一、再做多套路由选择算法 → 适配器+策略组合。
一、策略模式实战(模板方法增强版,你项目里 TaskSubmitStrategy / ChatStrategy 同类方案)
核心定义
策略模式(行为型):定义一套统一契约,多个实现类本身就是该能力的原生实现,内部是平等、可互相替换的不同业务算法;上下文负责路由选择,目的是运行时切换算法,消除大量if分支。
重点:策略实现类不包装任何外部不兼容的SDK,算法本体就在策略类内部。
适用场景
AI意图处理器、任务提交(同步/异步/延迟)、报表生成、标签查询,分支持续新增,公共逻辑多,适合搭配模板方法下沉通用逻辑。
完整代码
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.util.Map;
// ===================== 1.抽象策略基类:策略 + 模板方法混合 =====================
public abstract class AbstractMetricStrategy {
/** 策略唯一key,和@Component beanName对齐 */
public abstract String getStrategyKey();
/** 差异化核心逻辑,子类重写 */
public abstract void push(String metricKey, Map<String, Object> data);
// 模板方法:公共逻辑,所有策略默认复用
public void preCheck(Map<String, Object> data) {
if(data == null || data.isEmpty()){
throw new IllegalArgumentException("指标数据不能为空");
}
}
public void recordLog(String metricKey, Map<String, Object> data) {
System.out.println("[策略公共日志] metricKey=" + metricKey);
}
}
// ===================== 2.具体策略实现(原生实现,无外部Adaptee包装) =====================
@Component("sync")
public class SyncMetricStrategy extends AbstractMetricStrategy {
@Override
public String getStrategyKey() {
return "sync";
}
@Override
public void push(String metricKey, Map<String, Object> data) {
preCheck(data);
// 算法本体:同步直接本地打印上报
System.out.println("[同步策略] 直接上报指标:" + metricKey + ",数据:" + data);
recordLog(metricKey, data);
}
}
@Component("async")
public class AsyncMetricStrategy extends AbstractMetricStrategy {
@Override
public String getStrategyKey() {
return "async";
}
@Override
public void push(String metricKey, Map<String, Object> data) {
preCheck(data);
// 算法本体:丢入MQ异步
System.out.println("[异步策略] 投递MQ,指标:" + metricKey);
recordLog(metricKey, data);
}
}
@Component("defaultMetric")
public class DefaultMetricStrategy extends AbstractMetricStrategy {
@Override
public String getStrategyKey() {
return "defaultMetric";
}
@Override
public void push(String metricKey, Map<String, Object> data) {
preCheck(data);
System.out.println("[兜底策略] 默认日志落盘上报");
recordLog(metricKey, data);
}
}
// ===================== 3.上下文调度器 Context =====================
@Service
public class MetricStrategyContext {
private final Map<String, AbstractMetricStrategy> strategyMap;
public MetricStrategyContext(Map<String, AbstractMetricStrategy> strategyMap) {
this.strategyMap = strategyMap;
}
public void pushByStrategy(String strategyKey, String metricKey, Map<String, Object> data){
AbstractMetricStrategy strategy = strategyMap.getOrDefault(strategyKey, strategyMap.get("defaultMetric"));
strategy.push(metricKey, data);
}
}
调用示例
@RestController
@RequestMapping("/metric/strategy")
public class MetricStrategyController {
private final MetricStrategyContext metricStrategyContext;
public MetricStrategyController(MetricStrategyContext metricStrategyContext) {
this.metricStrategyContext = metricStrategyContext;
}
@GetMapping("/push")
public String push(@RequestParam String strategyKey){
metricStrategyContext.pushByStrategy(strategyKey, "user_login", Map.of("userId",10001));
return "ok";
}
}
✅ 特点总结
- 每个策略类本身就是算法主体,没有包装第三方异构SDK;
- 抽象基类承载模板逻辑,子类只写差异化;
- 新增上报方式,只新增策略类,调度代码不动,开闭原则;
- Spring Map注入只是路由工具,工具本身≠策略模式。
二、适配器模式实战(对象适配器,对标Spring HandlerAdapter,抹平异构SDK)
核心定义
适配器模式(结构型):已有一批接口签名完全不统一、不能修改源码的第三方/历史遗留组件,新增一层包装(Adapter),对外统一暴露一套Target接口;目的是消除接口不兼容,而不是切换算法。
重点:Adapter内部一定持有一个Adaptee(被适配对象),在方法内部做参数翻译、签名转换、异常包装。
Java工程几乎只用【对象适配器(组合)】,类适配器受单继承限制基本废弃。
适用场景
对接不同厂商SDK、不同埋点组件、多种存储客户端(OSS/乐享/销售易),也就是你代码库里 DifyClient、AliOssFileHandler 这类。
完整代码
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.util.Map;
// ===================== 1. Target 统一目标接口(对外标准契约) =====================
public interface MetricAdapter {
void push(String metricKey, Map<String, Object> data);
}
// ===================== 2. Adaptee:三方/历史组件,源码不可修改,签名五花八门 =====================
// 老埋点组件
public class OldTrackService {
public void reportEvent(String eventCode, Map<String,Object> params) {
System.out.println("[老埋点SDK原始方法] eventCode:" + eventCode + ", params:" + params);
}
}
// 日志采集组件
public class LogCollector {
public void collect(String type, Object data) {
System.out.println("[日志采集SDK原始方法] type:" + type);
}
}
// 云监控SDK
public class CloudMonitorSdk {
public void send(String metricName, Object value, long timestamp) {
System.out.println("[云监控SDK原始方法] metricName:" + metricName + ", ts:" + timestamp);
}
}
// ===================== 3. Adapter:适配器,包装Adaptee,翻译签名 =====================
@Component("track")
public class TrackMetricAdapter implements MetricAdapter {
private final OldTrackService oldTrackService;
public TrackMetricAdapter(OldTrackService oldTrackService) {
this.oldTrackService = oldTrackService;
}
@Override
public void push(String metricKey, Map<String, Object> data) {
// 适配翻译:统一接口 → 调用异构SDK的不同方法
oldTrackService.reportEvent(metricKey, data);
}
}
@Component("log")
public class LogMetricAdapter implements MetricAdapter {
private final LogCollector logCollector;
public LogMetricAdapter(LogCollector logCollector) {
this.logCollector = logCollector;
}
@Override
public void push(String metricKey, Map<String, Object> data) {
logCollector.collect(metricKey, data);
}
}
@Component("cloud")
public class CloudMetricAdapter implements MetricAdapter {
private final CloudMonitorSdk cloudMonitorSdk;
public CloudMetricAdapter(CloudMonitorSdk cloudMonitorSdk) {
this.cloudMonitorSdk = cloudMonitorSdk;
}
@Override
public void push(String metricKey, Map<String, Object> data) {
// 适配翻译:补齐SDK需要的时间戳参数
cloudMonitorSdk.send(metricKey, data, System.currentTimeMillis());
}
}
// ===================== 4. 调度上下文(简易查表,只是选择壳子) =====================
@Service
public class MetricAdapterContext {
private final Map<String, MetricAdapter> adapterMap;
public MetricAdapterContext(Map<String, MetricAdapter> adapterMap) {
this.adapterMap = adapterMap;
}
public void pushByAdapter(String adapterKey, String metricKey, Map<String,Object> data){
MetricAdapter adapter = adapterMap.get(adapterKey);
if(adapter == null){
throw new IllegalArgumentException("不支持的上报渠道");
}
adapter.push(metricKey, data);
}
}
调用示例
@RestController
@RequestMapping("/metric/adapter")
public class MetricAdapterController {
private final MetricAdapterContext adapterContext;
public MetricAdapterController(MetricAdapterContext adapterContext) {
this.adapterContext = adapterContext;
}
@GetMapping("/push")
public String push(@RequestParam String adapterKey){
adapterContext.pushByAdapter(adapterKey, "user_login", Map.of("userId",10001));
return "ok";
}
}
✅ 特点总结
- Adapter内部持有异构Adaptee,核心动作是参数/签名翻译;
- 底层SDK不能修改,通过包装层统一对外;
- 长得和策略的Map路由一模一样!区分点只在:实现内部有没有包装外部组件;
- Spring经典对标:
HandlerAdapter,DispatcherServlet遍历supports匹配handler。
❗重点误区:这个Context查表逻辑只是调度壳子,不是策略模式!壳子通用,策略和适配器都能用。
三、适配器 + 策略模式 组合方案(业务高阶场景)
核心定义
两层职责分离:
- 底层:适配器层:抹平各个异构第三方SDK,全部翻译成一套统一接口(解决接口不兼容)
- 上层:策略层 :针对已经归一化后的接口,提供多套可替换的路由选择规则(解决"按什么规则选渠道")
这才是真正的组合模式:适配器负责【归一化能力】,策略负责【归一之后的选择算法】。
区别于前面两种:前面两种只有一层;这里是两层独立抽象。
适用场景
多存储渠道、多上报渠道,不仅SDK接口不一样,同时还要动态切换路由规则:按成本优先、按地域优先、按容量优先选择渠道。
完整代码
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.util.Map;
// ========== 第一层:适配器层,和第二节适配器代码一致,产出统一接口 MetricChannel ==========
public interface MetricChannel {
void push(String metricKey, Map<String, Object> data);
}
// 被适配的异构SDK
class OldTrackService {
public void reportEvent(String eventCode, Map<String,Object> params) {}
}
class CloudMonitorSdk {
public void send(String metricName, Object value, long ts) {}
}
// 适配器
@Component("trackChannel")
public class TrackChannelAdapter implements MetricChannel {
private final OldTrackService oldTrackService;
public TrackChannelAdapter(OldTrackService oldTrackService) {
this.oldTrackService = oldTrackService;
}
@Override
public void push(String metricKey, Map<String, Object> data) {
oldTrackService.reportEvent(metricKey, data);
}
}
@Component("cloudChannel")
public class CloudChannelAdapter implements MetricChannel {
private final CloudMonitorSdk cloudMonitorSdk;
public CloudChannelAdapter(CloudMonitorSdk cloudMonitorSdk) {
this.cloudMonitorSdk = cloudMonitorSdk;
}
@Override
public void push(String metricKey, Map<String, Object> data) {
cloudMonitorSdk.send(metricKey, data, System.currentTimeMillis());
}
}
// ========== 第二层:路由策略层!策略接口,定义【选择渠道的算法】 ==========
public interface ChannelRouteStrategy {
/** 根据业务条件,选择一个归一后的渠道 */
MetricChannel selectChannel(Map<String, Object> bizData, Map<String, MetricChannel> channelMap);
}
// 策略1:成本优先策略
@Component("costFirst")
public class CostFirstRouteStrategy implements ChannelRouteStrategy {
@Override
public MetricChannel selectChannel(Map<String, Object> bizData, Map<String, MetricChannel> channelMap) {
// 模拟规则:云监控成本低,优先走cloudChannel
return channelMap.get("cloudChannel");
}
}
// 策略2:地域优先策略
@Component("regionFirst")
public class RegionFirstRouteStrategy implements ChannelRouteStrategy {
@Override
public MetricChannel selectChannel(Map<String, Object> bizData, Map<String, MetricChannel> channelMap) {
// 模拟规则:国内用户走老埋点
return channelMap.get("trackChannel");
}
}
// ========== 顶层上下文:持有渠道集合 + 路由策略,两层调度 ==========
@Service
public class MetricRouteContext {
// 所有已经被适配器归一后的渠道
private final Map<String, MetricChannel> channelMap;
// 多套可切换的路由策略
private final Map<String, ChannelRouteStrategy> routeStrategyMap;
public MetricRouteContext(Map<String, MetricChannel> channelMap,
Map<String, ChannelRouteStrategy> routeStrategyMap) {
this.channelMap = channelMap;
this.routeStrategyMap = routeStrategyMap;
}
public void push(String routeStrategyKey, String metricKey, Map<String,Object> bizData){
ChannelRouteStrategy routeStrategy = routeStrategyMap.get(routeStrategyKey);
if(routeStrategy == null){
throw new IllegalArgumentException("路由策略不存在");
}
// 策略层:使用选择算法选出渠道
MetricChannel channel = routeStrategy.selectChannel(bizData, channelMap);
// 适配器归一后的统一调用
channel.push(metricKey, bizData);
}
}
调用示例
@RestController
@RequestMapping("/metric/combine")
public class MetricCombineController {
private final MetricRouteContext routeContext;
public MetricCombineController(MetricRouteContext routeContext) {
this.routeContext = routeContext;
}
// 传入路由策略key:costFirst / regionFirst
@GetMapping("/push")
public String push(@RequestParam String routeStrategyKey){
routeContext.push(routeStrategyKey, "user_pay", Map.of("region","cn"));
return "ok";
}
}
✅ 特点总结
- 分层清晰:适配器负责【异构归一】,策略负责【选择规则替换】;
- 渠道新增 → 新增适配器;路由规则新增 → 新增路由策略;两者独立迭代;
- 这才是严格意义上:适配器模式 + 策略模式组合使用。
四、三者横向对比表(评审直接复制)
| 对比维度 | 纯策略模式 | 纯适配器模式 | 适配器+策略组合 |
|---|---|---|---|
| 核心目的 | 替换不同业务算法,消灭if-else | 抹平异构、不兼容的第三方接口 | 先归一SDK,再动态切换路由选择算法 |
| 实现类内部 | 自己就是算法主体,不包装外部对象 | 内部持有Adaptee,做签名/参数翻译 | 两层:适配器包装SDK;策略写选择规则 |
| 接口来源 | 实现类原生满足该接口 | Adaptee原本不满足Target接口 | 适配器产出统一接口,策略依赖这个统一接口 |
| 典型问句 | 我要用哪一套处理逻辑? | 这个第三方SDK接口对不上,怎么包装? | 这么多渠道,按什么规则选? |
| 项目对应 | TaskSubmitStrategy、Chat意图处理器 | DifyClient包装、OSS适配器、HandlerAdapter | 多存储+成本/地域路由 |
五、高频踩坑总结(工程重点)
- 不要看到 Map + Context 就判定是策略。Map注入只是实例查找工具,两个模式都可以复用这个壳子,判断标准看实现内部;
- 适配器不要写复杂业务逻辑,职责只做转换、异常包装、参数映射;业务判断放到上层;
- 组合模式不要写混层:不要把路由选择逻辑写进Adapter,Adapter只负责适配;
- 策略基类不要无限膨胀:模板方法用久了容易变成上帝类,建议拆细分接口、细分抽象类;
- Key对齐坑:
@Component("xxx")、配置表key、getKey返回值三处不一致,容易静默降级,建议项目启动自检。
六、文末总结
- 策略:算法可替换,生来同契约
- 适配器:接口不兼容,加层做翻译
- 适配器+策略:先翻译归一,再选路由规则
这一套就是我们业务系统最常用的分支解耦方案,看懂这套,以后代码评审、面试遇到这类代码,一眼就能分清。