策略枚举模式:当枚举遇上策略,类型安全与优雅兼得
说实话,我第一次看到「策略枚举」这个组合时,心里是有点抵触的。枚举不就是一堆常量吗?策略模式不是应该用多态吗?这两者怎么凑到一起的?
直到我在一个支付项目中,被 switch-case 的噩梦折磨得够呛——每次新增一个支付渠道,就要改好几个地方的 if-else,改完还要担心漏掉某个分支。那时候我才意识到,嗯,枚举和策略的结合,其实是个被低估的好东西。
什么是策略枚举模式?
说白了,就是把策略模式的核心思想——「将算法封装成独立的对象」——用枚举来实现。每个枚举常量不仅是一个值,还自带一个方法实现。
你想想看,传统的策略模式需要定义接口、实现类、上下文,还要搞个工厂来创建策略对象。代码量不小,而且类型安全全靠运行时保证。策略枚举呢?一个枚举类搞定所有,编译时就确定了所有策略。
一个真实的场景:支付渠道与折扣策略
我在做电商平台时遇到过这样一个需求:不同的支付渠道有不同的折扣策略。
- 微信支付:满100减5
- 支付宝:打9折,上限20
- 银联支付:满200减30
- 信用卡支付:打85折,上限50
一开始我用 if-else 写的,后来改成策略接口+实现类,再后来发现——其实用枚举就够了,而且更干净。
Java 实现:枚举中的抽象方法
public enum PaymentChannel {
WECHAT {
@Override
public double calculateDiscount(double amount) {
// 满100减5
return amount >= 100 ? 5.0 : 0.0;
}
},
ALIPAY {
@Override
public double calculateDiscount(double amount) {
// 打9折,上限20
double discount = amount * 0.1;
return Math.min(discount, 20.0);
}
},
UNIONPAY {
@Override
public double calculateDiscount(double amount) {
// 满200减30
return amount >= 200 ? 30.0 : 0.0;
}
},
CREDIT_CARD {
@Override
public double calculateDiscount(double amount) {
// 打85折,上限50
double discount = amount * 0.15;
return Math.min(discount, 50.0);
}
};
// 抽象方法:每个枚举常量必须实现
public abstract double calculateDiscount(double amount);
// 公共方法:计算最终支付金额
public double calculateFinalAmount(double amount) {
double discount = calculateDiscount(amount);
return amount - discount;
}
}
调用起来就很简单了:
public class PaymentService {
public void processPayment(PaymentChannel channel, double amount) {
double finalAmount = channel.calculateFinalAmount(amount);
System.out.println("支付渠道: " + channel.name());
System.out.println("原价: " + amount + ", 实付: " + finalAmount);
}
}
// 使用示例
PaymentService service = new PaymentService();
service.processPayment(PaymentChannel.WECHAT, 150.0); // 实付145
service.processPayment(PaymentChannel.ALIPAY, 150.0); // 实付135
service.processPayment(PaymentChannel.UNIONPAY, 150.0); // 实付150(不满200)
service.processPayment(PaymentChannel.CREDIT_CARD, 150.0); // 实付127.5
C++ 实现:用函数指针或 std::function
C++ 的枚举不像 Java 那样能直接定义抽象方法。但我们可以换个思路——用枚举值映射到函数对象。
#include <iostream>
#include <functional>
#include <map>
#include <string>
#include <algorithm>
enum class PaymentChannel {
WECHAT,
ALIPAY,
UNIONPAY,
CREDIT_CARD
};
// 折扣策略函数类型
using DiscountStrategy = std::function<double(double)>;
// 策略映射表
std::map<PaymentChannel, DiscountStrategy> createStrategies() {
std::map<PaymentChannel, DiscountStrategy> strategies;
strategies[PaymentChannel::WECHAT] = [](double amount) {
return amount >= 100 ? 5.0 : 0.0;
};
strategies[PaymentChannel::ALIPAY] = [](double amount) {
double discount = amount * 0.1;
return std::min(discount, 20.0);
};
strategies[PaymentChannel::UNIONPAY] = [](double amount) {
return amount >= 200 ? 30.0 : 0.0;
};
strategies[PaymentChannel::CREDIT_CARD] = [](double amount) {
double discount = amount * 0.15;
return std::min(discount, 50.0);
};
return strategies;
}
double calculateFinalAmount(PaymentChannel channel, double amount) {
static auto strategies = createStrategies();
double discount = strategies[channel](amount);
return amount - discount;
}
int main() {
std::cout << "微信支付 150元: "
<< calculateFinalAmount(PaymentChannel::WECHAT, 150.0) << std::endl;
std::cout << "支付宝 150元: "
<< calculateFinalAmount(PaymentChannel::ALIPAY, 150.0) << std::endl;
return 0;
}
策略枚举的核心优势
| 特性 | 传统策略模式 | 策略枚举模式 |
|---|---|---|
| 类型安全 | 运行时检查 | 编译时确定 |
| 代码量 | 接口+多个实现类+工厂 | 一个枚举类 |
| 扩展性 | 新增类,符合开闭原则 | 新增枚举常量,也符合 |
| 可读性 | 需要查找实现类 | 所有策略一目了然 |
| 序列化 | 需要额外处理 | 枚举天然支持 |
什么时候用策略枚举?
我个人总结了几条经验:
- 策略数量固定且较少(比如少于10个)——枚举很适合
- 策略逻辑简单——每个策略就几行代码,写在枚举里很清爽
- 需要类型安全——比如支付渠道、订单状态、错误码这些
- 策略和枚举值一一对应——不需要复杂的组合策略
但如果你有大量策略(几十上百个),或者策略需要动态组合(比如折扣叠加),那还是老老实实用传统策略模式吧。枚举不是万能的。
避坑指南:我曾经踩过的坑
我曾经在一个项目中,把策略枚举的抽象方法写得特别复杂,每个枚举常量里塞了上百行代码。结果呢?一个枚举文件三千行,看着就头疼。后来我重构了——把复杂的逻辑拆到单独的 helper 类里,枚举只做策略的「入口」和「路由」。
还有一次,我在枚举里加了状态字段,然后试图用枚举来管理状态机的流转。嗯,那是个错误。枚举适合「静态的策略」,不适合「动态的状态流转」。状态机还是得用状态模式。
策略枚举的知识体系
下面这张图,是我梳理的策略枚举模式的核心逻辑:
总结一下
策略枚举模式,说白了就是「用枚举来组织策略」。它把策略的定义、分组、调用都压缩到一个枚举类里,既保证了类型安全,又减少了代码量。
我个人觉得,这种模式特别适合那些「策略数量不多、逻辑不复杂、且与枚举值强绑定」的场景。比如支付渠道、折扣规则、状态处理、错误码映射等等。
但也要记住——它不是万能的。策略多了、复杂了、需要动态组合了,还是得回到传统的策略模式。设计模式没有银弹,只有合适的场景和合适的取舍。
公众号:蓝海资料掘金营,微信deep3321