如何优雅管理接口的多个实现
痛点场景
在业务开发中,经常遇到这样的需求:一个接口,多个实现类,运行时根据某个类型值动态选择具体实现。
最常见的写法就是 if-else 或 switch:
java
public Result handle(String type, Request req) {
if ("A".equals(type)) {
return new HandlerA().execute(req);
} else if ("B".equals(type)) {
return new HandlerB().execute(req);
} else if ("C".equals(type)) {
return new HandlerC().execute(req);
} else {
throw new UnsupportedException();
}
}
问题 :每增加一种类型,就要修改这段入口代码,违反了 开闭原则(对扩展开放,对修改关闭)。当类型多达几十种时,这个类会变得臃肿且极易出错。
核心解决思路
采用 策略模式 + 工厂模式 :将"根据类型获取具体实现"的逻辑从硬编码改为 配置映射 。新增实现时,只需要添加新类,入口代码 一行都不用改。
在 Spring 环境下,可以利用依赖注入特性,自动收集所有实现类并组装成一个 Map<String, 接口>,类型值作为 key,实现实例作为 value。
几种具体实现方案
方案一:注入 List + 手动转 Map
java
// 1. 定义接口,增加一个返回类型标识的方法
public interface Processor {
String getType(); // 返回 "A", "B", "C" 等唯一标识
void process();
}
// 2. 实现类标记 @Component
@Component
public class AProcessor implements Processor {
@Override
public String getType() { return "A"; }
@Override
public void process() { /* 业务逻辑 */ }
}
@Component
public class BProcessor implements Processor {
@Override
public String getType() { return "B"; }
@Override
public void process() { /* 业务逻辑 */ }
}
// 3. 工厂/注册类,自动组装 Map
@Service
public class ProcessorRegistry {
@Autowired
private List<Processor> processors; // Spring 会把所有 Processor 实现注入进来
private Map<String, Processor> processorMap;
@PostConstruct
public void init() {
processorMap = processors.stream()
.collect(Collectors.toMap(Processor::getType, Function.identity()));
}
public Processor getProcessor(String type) {
Processor p = processorMap.get(type);
if (p == null) throw new IllegalArgumentException("不支持的类型: " + type);
return p;
}
}
// 4. 入口调用
@Service
public class EntryService {
@Autowired
private ProcessorRegistry registry;
public void execute(String type) {
registry.getProcessor(type).process();
}
}
优点 :完全符合开闭原则,新增 CProcessor 只需加一个类,ProcessorRegistry 和 EntryService 零修改。
方案二:直接注入 Map<String, 接口>
Spring 支持直接将多个实现注入为 Map,其中 key 是 Bean 名称(默认为类名首字母小写)。
java
@Service
public class EntryService {
@Autowired
private Map<String, Processor> processorMap; // key="AProcessor", value=实例
public void execute(String beanName) {
processorMap.get(beanName).process();
}
}
局限 :key 是 Bean 名称而非业务 type,调用方需要知道实现类的 Bean 名称,不够语义化。除非配合 @Qualifier 或自定义注解,但方案一更直观。
方案三:ApplicationContext 编程式获取
适合编写通用框架或工具类:
java
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext ctx) {
context = ctx;
}
public static <T> Map<String, T> getBeansOfType(Class<T> type) {
return context.getBeansOfType(type);
}
}
// 使用
Map<String, Processor> map = SpringContextUtil.getBeansOfType(Processor.class);
方案四:非 Spring 环境(原生 Java)
使用 ServiceLoader,但需要手动在 META-INF/services/ 下配置接口文件。
java
ServiceLoader<Processor> loader = ServiceLoader.load(Processor.class);
Map<String, Processor> map = new HashMap<>();
for (Processor p : loader) {
map.put(p.getType(), p);
}
进阶:复杂条件匹配
如果选择实现类不单靠一个 type 字符串,而是需要根据多个参数动态判断(如请求内容、用户等级等),可以把判断逻辑封装到每个实现类内部:
java
public interface MatcherProcessor {
boolean match(Request req); // 实现类自己判断是否匹配
void process(Request req);
}
// 注册时不用 Map,而是用 List,调用时遍历找到第一个 match 返回 true 的
这就是 责任链模式 的雏形,同样能做到零修改扩展。
总结
| 方案 | 适用场景 | 是否需要修改入口 |
|---|---|---|
| if-else | 类型极少且不会变化 | 是 |
| 注入 List + 转 Map | Spring 项目,业务 type 为 key | 否 |
| 直接注入 Map | Spring 项目,可用 Bean 名称作 key | 否,但 key 不友好 |
| ServiceLoader | 非 Spring,简单场景 | 否,但需配置文件 |
| 责任链 | 复杂条件匹配 | 否 |
核心思想 :把"多实现的管理"从硬编码逻辑变成 数据驱动 。Spring 的 List 注入 + @PostConstruct 转 Map 是最优雅、最常用的方案。
下次新增实现类时,你只需要:
- 新建一个类实现接口
- 加上
@Component - 定义好自己的
getType()返回值
其他所有代码,尤其是入口调用处,永远不需要再改动。
愿你我都能在各自的领域里不断成长,勇敢追求梦想,同时也保持对世界的好奇与善意!