在复杂的支付系统中,我们经常遇到这样的场景:一个支付请求可能需要经过多个账户的校验和处理,比如先检查企业账户余额,再检查个人账户余额,最后可能还需要使用优惠券抵扣。传统的if-else嵌套写法不仅难以维护,还会随着业务扩展变得越来越臃肿。这正是责任链模式大显身手的地方。
责任链模式本质上是一个链表结构,每个节点(处理器)都有相同的接口,但各自实现不同的处理逻辑。当请求到来时,它会沿着链条传递,直到被某个处理器完全处理或到达链条末端。
这种设计带来三个显著优势:
提示:在设计责任链时,处理器的顺序往往代表业务优先级。比如企业账户通常应该优先于个人账户处理。
让我们通过一个完整的支付系统案例,看看如何实现责任链模式。这个系统需要支持:
首先定义处理器通用接口,这是责任链模式的核心契约:
java复制public interface PaymentHandler {
/**
* 判断该处理器是否适用于当前用户
*/
boolean canProcess(String userId);
/**
* 执行支付操作
*/
PaymentResult processPayment(String userId, double amount);
}
class PaymentResult {
private boolean success;
private double paidAmount;
private String message;
// 构造方法和getter省略
}
企业账户处理器实现:
java复制public class CorporatePaymentHandler implements PaymentHandler {
private static final double MAX_CORPORATE_PAYMENT = 5000;
@Override
public boolean canProcess(String userId) {
return userId.startsWith("emp_"); // 企业用户ID前缀
}
@Override
public PaymentResult processPayment(String userId, double amount) {
if (amount <= MAX_CORPORATE_PAYMENT) {
return new PaymentResult(true, amount, "企业账户支付成功");
} else {
return new PaymentResult(true, MAX_CORPORATE_PAYMENT,
String.format("企业账户部分支付成功(%.2f)", MAX_CORPORATE_PAYMENT));
}
}
}
个人账户处理器实现类似,只是限额和判断逻辑不同。
java复制public class CompositePaymentContext {
private final List<PaymentHandler> handlers = new ArrayList<>();
public void addHandler(PaymentHandler handler) {
handlers.add(handler);
}
public Map<String, PaymentResult> executePayment(String userId, double totalAmount) {
Map<String, PaymentResult> results = new LinkedHashMap<>();
double remaining = totalAmount;
for (PaymentHandler handler : handlers) {
if (handler.canProcess(userId) && remaining > 0) {
PaymentResult result = handler.processPayment(userId, remaining);
results.put(handler.getClass().getSimpleName(), result);
if (result.isSuccess()) {
remaining -= result.getPaidAmount();
if (remaining <= 0) break;
}
}
}
if (remaining > 0) {
results.put("Remaining", new PaymentResult(false, remaining,
String.format("支付未完成,剩余金额: %.2f", remaining)));
}
return results;
}
}
在实际项目中应用责任链模式时,有几个关键点需要注意:
链条顺序决定业务优先级:在我们案例中,企业账户处理器应该先于个人账户处理器加入链条,这代表了"优先使用企业账户"的业务规则。
处理器的幂等性设计:支付操作可能因为网络等原因需要重试,处理器应该设计为可重复执行而不产生副作用。
上下文信息的传递:如果处理器之间需要共享数据(如风控信息),可以设计一个上下文对象贯穿整个链条。
性能监控点:在每个处理器的关键路径添加监控点,便于后续分析支付失败原因。
我在重构神州专车支付系统时,就遇到过处理器顺序配置错误导致业务逻辑异常的情况。后来我们通过在处理器接口中添加getPriority()方法,由系统自动排序,避免了人工错误。
当业务规则频繁变化时,硬编码的条件判断会成为维护噩梦。规则引擎正是为了解决这个问题而生,它实现了业务规则与代码的分离。
相比传统编码方式,规则引擎带来三大优势:
以电商促销活动为例,我们使用轻量级的AviatorScript规则引擎实现满减规则:
业务人员可以这样配置满减规则:
java复制String rule = "if (amount >= 1000) {\n" +
" return 200;\n" +
"} elsif (amount >= 500) {\n" +
" return 100;\n" +
"} else {\n" +
" return 0;\n" +
"}";
这个规则可以存储在数据库或配置中心,修改时无需发布代码。
java复制public static BigDecimal getDiscount(BigDecimal amount, String rule) {
Map<String, Object> env = new HashMap<>();
env.put("amount", amount.doubleValue());
// 编译并缓存规则(MD5作为key)
Expression expression = AviatorEvaluator.compile(
DigestUtils.md5Hex(rule.getBytes()),
rule,
true);
Object result = expression.execute(env);
return result != null ? new BigDecimal(result.toString()) : BigDecimal.ZERO;
}
虽然规则引擎很强大,但并非所有场景都适用。根据我的经验,以下情况更适合引入规则引擎:
而以下情况可能不适合:
策略模式定义了一系列算法,并将每个算法封装起来,使它们可以互相替换。支付方式选择是策略模式的典型场景:
java复制public interface PaymentStrategy {
PaymentResult pay(Order order);
}
public class CreditCardStrategy implements PaymentStrategy {
public PaymentResult pay(Order order) {
// 信用卡支付逻辑
}
}
public class PaymentContext {
private PaymentStrategy strategy;
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public PaymentResult executePayment(Order order) {
return strategy.pay(order);
}
}
SPI(Service Provider Interface)提供了更松耦合的扩展方式。以JDBC驱动为例:
java复制ServiceLoader<Driver> drivers = ServiceLoader.load(Driver.class);
for (Driver driver : drivers) {
// 注册驱动
}
选择策略模式还是SPI,主要考虑以下因素:
| 维度 | 策略模式 | SPI机制 |
|---|---|---|
| 耦合度 | 编译时依赖 | 运行时发现 |
| 变更成本 | 需要修改代码 | 修改配置即可 |
| 适用场景 | 策略已知且有限 | 需要动态扩展 |
| 性能 | 直接调用,效率高 | 反射创建,有一定开销 |
在实际项目中,我经常将两者结合使用:核心策略使用策略模式实现,而可插拔的扩展功能通过SPI机制提供。
结合多种模式,我们可以设计一个灵活的订单处理系统:
java复制public class OrderProcessor {
private ValidationChain validationChain;
private PricingEngine pricingEngine;
private PaymentRouter paymentRouter;
public OrderResult process(Order order) {
// 1. 验证
ValidationResult vr = validationChain.validate(order);
if (!vr.isValid()) return failResult(vr);
// 2. 计算价格
OrderPrice price = pricingEngine.calculate(order);
// 3. 支付
PaymentResult pr = paymentRouter.route(order.getPaymentMethod())
.execute(price.getActualAmount());
return buildResult(price, pr);
}
}
在设计模式应用中,我们需要特别注意:
我在实际项目中总结出一个经验法则:当某种条件判断在三个月内修改超过两次,就值得考虑用设计模式重构。
无论采用哪种模式,都需要完善的监控:
我们通过在基础框架中植入埋点,实现了这些指标的自动采集和可视化。