设计模式(三)——优雅的策略模式
要优雅地使用策略模式来消除 if-else
很多老项目的代码,充斥着大量的 if-else 。
比如 if ("ALIPAY".equals(type)) { ... } else if ("WECHAT".equals(type)) { ... },这种代码不仅难看,而且每次新增一种支付方式都要修改主逻辑,违反了开闭原则 (OCP)。
在 Spring Boot 中,利用 IOC 容器的自动注入特性,我们可以极其优雅地实现策略模式,完全消除 if-else。
场景设定
我们需要处理订单支付,目前支持 支付宝 (AliPay) 和 微信支付 (WeChatPay),未来可能会加 银联支付。
原写法:
// 订单服务(客户代码:调用支付算法的模块)
public class OrderService {
// 支付方法(客户代码直接耦合所有支付算法)
public void pay(Order order, String payType) {
// 客户代码中直接包含所有算法的判断和调用
if ("WECHAT".equals(payType)) {
// 微信支付算法(直接写在客户代码中,或直接调用具体类)
System.out.println("微信支付:生成签名=" + order.getOrderNo() + ",扣减金额=" + order.getAmount());
// 后续如果修改微信签名逻辑,必须改这里
} else if ("ALIPAY".equals(payType)) {
// 支付宝支付算法
System.out.println("支付宝支付:生成订单号=" + order.getOrderNo() + ",扣减金额=" + order.getAmount());
} else {
throw new IllegalArgumentException("不支持的支付方式");
}
}
}
定义策略接口 (The Standard)
首先定义一个统一的接口,所有支付方式都要遵循这个标准。
public interface PaymentStrategy {
/**
* 核心支付逻辑
* @param orderId 订单ID
* @param amount 金额
* @return 支付结果
*/
String pay(Long orderId, double amount);
/**
* 策略标识(核心:用来区分是哪种策略)
* @return 支付类型,如 "ALI_PAY", "WECHAT_PAY"
*/
String getType();
}
具体策略实现 (The Components)
创建具体的支付实现类,并使用 @Component (或 @Service) 交给 Spring 管理。
策略 A:支付宝
@Component
public class AliPayStrategy implements PaymentStrategy {
@Override
public String pay(Long orderId, double amount) {
// 这里对接支付宝SDK...
return "支付宝支付成功:订单 " + orderId + ",金额 " + amount;
}
@Override
public String getType() {
return "ALI_PAY"; // 标识自己是支付宝
}
}
策略 B:微信支付
@Component
public class WeChatPayStrategy implements PaymentStrategy {
@Override
public String pay(Long orderId, double amount) {
// 这里对接微信SDK...
return "微信支付成功:订单 " + orderId + ",金额 " + amount;
}
@Override
public String getType() {
return "WECHAT_PAY"; // 标识自己是微信
}
}
策略工厂 (The Magic Factory) —— 核心步骤
下面这种写法是:工厂+策略的写法,如果需要多次复用策略建议采用此方法。
这是最能体现 Spring 优雅的地方。我们不需要手动 new AliPayStrategy(),而是让 Spring 帮我们把所有实现了 PaymentStrategy 接口的 Bean 自动注入到一个 Map 中。
@Component
public class PaymentFactory {
// 【核心魔法】:Spring 会自动把所有 PaymentStrategy 的实现类注入到这个 Map 中
// key = Bean 的名称(默认类名首字母小写),value = 对应的 Bean 对象
// 但为了更灵活,我们通常配合 @PostConstruct 自己构建一个 Map
private final Map<String, PaymentStrategy> strategyMap = new ConcurrentHashMap<>();
// 构造函数注入所有的 Strategy 实现类 (List形式)
// 这样是可以做到的,详见下方链接
private final List<PaymentStrategy> strategyList;
public PaymentFactory(List<PaymentStrategy> strategyList) {
this.strategyList = strategyList;
}
// 在 Bean 初始化完成后,自动执行该方法,构建查找表
@PostConstruct
private void init() {
for (PaymentStrategy strategy : strategyList) {
// key 使用我们在接口中定义的 getType(),比用 BeanName 更可控
strategyMap.put(strategy.getType(), strategy);
}
}
// 对外提供的方法:根据 type 获取对应的策略
public PaymentStrategy getStrategy(String type) {
PaymentStrategy strategy = strategyMap.get(type);
if (strategy == null) {
throw new IllegalArgumentException("暂不支持该支付方式: " + type);
}
return strategy;
}
}
- 如果不依赖 Map,也能实现抛弃
if-else的写法:
这种方法甚至没用到简单工厂模式,是原汁原味的策略模式。即不返回策略对象,而是直接调用策略方法
// 1. 抽象策略接口(定义支付算法的统一规范,客户代码只依赖该接口)
public interface PaymentStrategy {
void pay(Order order);
}
// 2. 具体策略类(封装各自的支付算法,算法变化只修改这里)
// 微信支付策略
public class WeChatPaymentStrategy implements PaymentStrategy {
@Override
public void pay(Order order) {
System.out.println("微信支付:生成签名=" + order.getOrderNo() + ",扣减金额=" + order.getAmount());
}
}
// 支付宝支付策略
public class AlipayPaymentStrategy implements PaymentStrategy {
@Override
public void pay(Order order) {
System.out.println("支付宝支付:生成订单号=" + order.getOrderNo() + ",扣减金额=" + order.getAmount());
}
}
// 3. 环境类(客户代码:持有抽象策略接口引用,不依赖任何具体算法)
public class OrderService {
// 客户代码只依赖抽象策略接口,不关心具体实现
public void pay(Order order, PaymentStrategy paymentStrategy) {
// 调用算法:只调用接口方法,无任何具体算法逻辑
paymentStrategy.pay(order);
}
}
话说,像这样,我单纯只使用策略模式,也能做到去掉if-else的效果啊,为什么要多此一举弄个工厂呢
见:纯策略 VS 工厂+策略
业务调用 (Usage)
现在,在我们的 Controller 或 Service 中,代码会变得极其干净。
@RestController
@RequestMapping("/pay")
public class PayController {
@Autowired
private PaymentFactory paymentFactory;
@PostMapping
public String pay(@RequestParam String type,
@RequestParam Long orderId,
@RequestParam double amount) {
// 1. 一行代码获取对应策略,完全没有 if-else
PaymentStrategy strategy = paymentFactory.getStrategy(type);
// 2. 执行支付
return strategy.pay(orderId, amount);
}
}
为什么说这很优雅?
- 消除了
if-else:主逻辑(Factory 和 Controller)中看不到任何条件判断。 - 符合开闭原则 (OCP):
如果你明天要加一个“银联支付”,你只需要:- 新建一个
UnionPayStrategy实现接口。 - 加上
@Component注解。 - 其他代码(Factory、Controller)一行都不用改! Spring 会自动扫描到新 Bean 并加入 List,工厂自动注册。
- 新建一个
- 类型安全:通过
getType()方法(甚至可以使用 Enum 代替 String)来管理类型,比直接依懒 Bean 的名称(aliPayStrategy)更健壮。
还可以更高级吗?
变种:利用枚举 (Enum) 结合
如果你的业务类型是固定的枚举,可以将策略逻辑直接绑定在枚举中(适合逻辑简单的场景),或者让工厂 Map 的 Key 变成枚举。
// 使用 Enum 作为 Map 的 Key,更加类型安全
private final Map<PaymentTypeEnum, PaymentStrategy> strategyMap = ...
变种:模版方法 + 策略
如果所有支付流程都大致相同(验签 -> 核心支付 -> 记日志),你可以搞一个 AbstractPaymentStrategy 实现公共逻辑,只把差异化的核心支付逻辑留给子类,这就是 模板方法模式 + 策略模式 的混合双打。
下一步建议
如果你想挑战更有难度的场景,比如“多个策略同时生效”(例如:搞活动时,既要打折策略,又要积分策略),这种应该怎么设计?
见:管道模式
更多推荐
所有评论(0)