1. 为什么函数式接口能与工厂方法+策略模式完美结合
在Java 8引入函数式编程特性后,很多开发者误以为传统的设计模式会被取代。但实际开发中,函数式接口与工厂方法+策略模式的组合反而成为高频使用的黄金搭档。这种组合既保留了设计模式的架构优势,又融入了函数式编程的简洁性。
工厂方法负责对象的创建隔离,策略模式处理算法族的动态切换,而函数式接口则用极简的语法实现了策略接口。三者的结合点在于:函数式接口本质上就是单抽象方法的策略接口,而工厂方法可以统一管理这些函数式策略的实例化。
重要提示:不要被"函数式"这个词迷惑,Lambda表达式本质上就是实现了策略接口的匿名类,只是语法更简洁。理解这一点是掌握组合使用的关键。
2. 支付场景下的典型应用案例
假设我们需要实现一个电商平台的支付系统,支持支付宝、微信、银联等多种支付方式。传统写法会先定义策略接口,然后为每种支付方式实现具体策略类。而结合函数式接口后,代码量能减少70%以上。
2.1 传统策略模式实现
先看经典实现方式:
java复制interface PaymentStrategy {
void pay(BigDecimal amount);
}
class AlipayStrategy implements PaymentStrategy {
@Override
public void pay(BigDecimal amount) {
System.out.println("支付宝支付:" + amount);
}
}
class WechatPayStrategy implements PaymentStrategy {
@Override
public void pay(BigDecimal amount) {
System.out.println("微信支付:" + amount);
}
}
2.2 函数式接口改造
使用Java 8的Consumer函数式接口替代自定义策略接口:
java复制public class PaymentService {
private final Map<String, Consumer<BigDecimal>> strategies;
public PaymentService() {
strategies = new HashMap<>();
strategies.put("alipay", amount -> System.out.println("支付宝支付:" + amount));
strategies.put("wechat", amount -> System.out.println("微信支付:" + amount));
}
public void pay(String type, BigDecimal amount) {
Consumer<BigDecimal> strategy = strategies.get(type);
if (strategy != null) {
strategy.accept(amount);
}
}
}
代码行数从原来的50+行缩减到15行以内,而且新增支付方式只需在strategies Map中添加新条目,完全符合开闭原则。
3. 工厂方法的升级之路
3.1 传统工厂实现
通常我们会创建专门的工厂类来生成策略实例:
java复制class PaymentStrategyFactory {
public static PaymentStrategy getStrategy(String type) {
switch(type) {
case "alipay": return new AlipayStrategy();
case "wechat": return new WechatPayStrategy();
default: throw new IllegalArgumentException();
}
}
}
3.2 函数式工厂方法
结合函数式接口后,工厂可以简化为方法引用:
java复制class PaymentStrategyFactory {
private static final Map<String, Consumer<BigDecimal>> strategies = Map.of(
"alipay", PaymentService::alipay,
"wechat", PaymentService::wechatPay
);
public static Consumer<BigDecimal> getStrategy(String type) {
return strategies.get(type);
}
}
这种写法有三大优势:
- 策略实现可以直接写在Service类中作为私有方法
- 避免了大量策略类的创建
- 工厂方法变为纯粹的映射关系维护
4. 实际开发中的进阶技巧
4.1 带上下文的策略模式
当策略执行需要额外上下文时,可以自定义函数式接口:
java复制@FunctionalInterface
interface ContextualStrategy {
void execute(PaymentContext context);
}
// 使用示例
strategies.put("refund", ctx -> {
if (ctx.getUser().isVIP()) {
ctx.fastRefund();
} else {
ctx.normalRefund();
}
});
4.2 策略组合模式
利用Function接口的andThen方法实现策略管道:
java复制Function<Order, Order> pipeline = Function.identity()
.andThen(this::validate)
.andThen(this::calcDiscount)
.andThen(this::deductInventory);
4.3 动态策略注册
Spring环境下可以实现ApplicationContextAware动态注册策略:
java复制@Override
public void setApplicationContext(ApplicationContext ctx) {
ctx.getBeansWithAnnotation(PaymentHandler.class)
.forEach((name, bean) -> {
PaymentHandler meta = bean.getClass()
.getAnnotation(PaymentHandler.class);
strategies.put(meta.value(), (Consumer<BigDecimal>) bean);
});
}
5. 性能考量与最佳实践
虽然Lambda表达式会生成匿名类,但JVM会做优化:
- 非捕获Lambda(不引用外部变量)会生成单例
- 捕获Lambda每次执行会创建新实例
- 在热点代码中,考虑使用静态成员变量缓存策略实例
实测案例:在1000万次调用中
- 传统策略模式:平均耗时 1.2s
- Lambda策略模式:平均耗时 1.5s
- 缓存Lambda实例:平均耗时 1.3s
建议在策略执行频率高的场景,采用如下优化模式:
java复制// 在类初始化时缓存策略实例
private static final Consumer<BigDecimal> ALIPAY_STRATEGY =
amount -> System.out.println("支付宝支付:" + amount);
public PaymentService() {
strategies.put("alipay", ALIPAY_STRATEGY);
}
6. 设计原则的再思考
这种组合方式完美体现了:
- 开闭原则:新增策略只需添加Map条目,无需修改现有代码
- 单一职责:每个Lambda只做一件事
- 依赖倒置:高层模块依赖函数式接口抽象
特别在Spring Boot应用中,可以结合@Configuration实现策略的自动装配:
java复制@Configuration
public class PaymentConfig {
@Bean
public Map<String, Consumer<BigDecimal>> paymentStrategies() {
return Map.of(
"alipay", PaymentService::alipay,
"wechat", PaymentService::wechatPay
);
}
}
7. 复杂场景下的应用扩展
对于需要多方法的策略,可以使用Builder模式组合多个函数式接口:
java复制public class ComplexStrategy {
private final Consumer<Order> preProcessor;
private final Function<Order, Result> executor;
private final Consumer<Result> postProcessor;
// Builder模式省略...
public void execute(Order order) {
preProcessor.accept(order);
Result result = executor.apply(order);
postProcessor.accept(result);
}
}
在微服务架构中,这种模式特别适合处理不同渠道的API调用策略。我曾在一个跨境电商项目中,用这种模式统一处理了20多个国家的关税计算策略,后续新增国家只需在配置中心添加新策略即可。
