我刚入行Java开发时,最头疼的就是那些满屏的if-else语句。记得有一次维护一个电商促销模块,光是计算优惠就有20多个条件分支,每次新增活动类型都战战兢兢。直到我学会了策略模式,才真正体会到设计模式的魅力。
策略模式的核心思想其实很简单:把会变化的算法部分抽取出来,封装成独立的策略类,让它们可以相互替换。这种变化独立于使用算法的客户,也就是我们常说的"对修改关闭,对扩展开放"。
举个例子,假设我们要实现一个支付系统:
java复制// 传统写法
public void pay(String paymentType, BigDecimal amount) {
if ("alipay".equals(paymentType)) {
// 支付宝支付逻辑
} else if ("wechat".equals(paymentType)) {
// 微信支付逻辑
} else if ("unionpay".equals(paymentType)) {
// 银联支付逻辑
}
// 每新增一种支付方式就要修改这里
}
这种写法至少有三大问题:
提示:当你在代码中看到超过3个if-else分支处理同一类问题时,就该考虑策略模式了。
一个标准的策略模式包含三个关键角色:
让我们用支付系统的例子来实现:
java复制// 策略接口
public interface PaymentStrategy {
void pay(BigDecimal amount);
}
// 具体策略类
public class AlipayStrategy implements PaymentStrategy {
@Override
public void pay(BigDecimal amount) {
System.out.println("使用支付宝支付:" + amount);
// 实际的支付宝支付逻辑
}
}
public class WechatPayStrategy implements PaymentStrategy {
@Override
public void pay(BigDecimal amount) {
System.out.println("使用微信支付:" + amount);
// 实际的微信支付逻辑
}
}
// 上下文类
public class PaymentContext {
private PaymentStrategy strategy;
public PaymentContext(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void executePayment(BigDecimal amount) {
strategy.pay(amount);
}
}
java复制public class Client {
public static void main(String[] args) {
// 使用支付宝支付
PaymentContext context = new PaymentContext(new AlipayStrategy());
context.executePayment(new BigDecimal("100.00"));
// 切换为微信支付
context = new PaymentContext(new WechatPayStrategy());
context.executePayment(new BigDecimal("200.00"));
}
}
这样设计的好处显而易见:
当策略较多时,可以使用工厂模式来管理策略对象的创建:
java复制public class StrategyFactory {
private static final Map<String, PaymentStrategy> strategies = new HashMap<>();
static {
strategies.put("alipay", new AlipayStrategy());
strategies.put("wechat", new WechatPayStrategy());
// 可以继续添加其他策略
}
public static PaymentStrategy getStrategy(String type) {
if (type == null || type.isEmpty()) {
throw new IllegalArgumentException("type should not be empty.");
}
return strategies.get(type);
}
}
// 使用方式
PaymentStrategy strategy = StrategyFactory.getStrategy("alipay");
PaymentContext context = new PaymentContext(strategy);
context.executePayment(amount);
在实际项目中,我们通常会结合Spring框架使用策略模式:
java复制// 定义策略接口
public interface FileParser {
String parse(File file);
}
// 具体策略实现
@Service("csvParser")
public class CsvParser implements FileParser {
@Override
public String parse(File file) {
// CSV解析逻辑
}
}
@Service("excelParser")
public class ExcelParser implements FileParser {
@Override
public String parse(File file) {
// Excel解析逻辑
}
}
// 使用Map自动注入所有实现
@RestController
public class FileController {
@Autowired
private Map<String, FileParser> parsers;
@PostMapping("/parse")
public String parseFile(@RequestParam String type, @RequestParam MultipartFile file) {
FileParser parser = parsers.get(type + "Parser");
if (parser == null) {
throw new IllegalArgumentException("Unsupported file type");
}
return parser.parse(convert(file));
}
}
这种方式的优势:
策略对象通常应该是无状态的,这样可以安全地在多个上下文中共享同一个策略实例。如果策略需要维护状态,应该考虑:
java复制// 无状态策略 - 可以共享实例
public class DiscountStrategy {
public BigDecimal apply(BigDecimal amount) {
return amount.multiply(BigDecimal.valueOf(0.9));
}
}
// 有状态策略 - 需要每次创建新实例
public class PersonalizedDiscountStrategy {
private final User user;
public PersonalizedDiscountStrategy(User user) {
this.user = user;
}
public BigDecimal apply(BigDecimal amount) {
// 根据用户属性计算折扣
}
}
策略选择可以基于:
java复制// 基于条件的自动选择
public class AutoSelectionContext {
private List<ConditionalStrategy> strategies;
public AutoSelectionContext(List<ConditionalStrategy> strategies) {
this.strategies = strategies;
}
public void execute(ContextData data) {
for (ConditionalStrategy strategy : strategies) {
if (strategy.canApply(data)) {
strategy.execute(data);
return;
}
}
throw new IllegalStateException("No applicable strategy found");
}
}
public interface ConditionalStrategy {
boolean canApply(ContextData data);
void execute(ContextData data);
}
当策略很多时,可能会导致类数量急剧增加。解决方案:
java复制// 使用lambda表达式
public class LambdaStrategy {
private final Map<StrategyType, Function<String, String>> strategies;
public LambdaStrategy() {
strategies = new HashMap<>();
strategies.put(StrategyType.A, s -> s.toUpperCase());
strategies.put(StrategyType.B, s -> s.toLowerCase());
// 可以继续添加其他策略
}
public String apply(StrategyType type, String input) {
return strategies.get(type).apply(input);
}
}
有时策略需要共享一些公共数据,可以通过:
java复制public class SharedDataContext {
private final Strategy strategy;
private final SharedData sharedData;
public SharedDataContext(Strategy strategy, SharedData sharedData) {
this.strategy = strategy;
this.sharedData = sharedData;
}
public void execute() {
strategy.execute(sharedData);
}
}
public interface Strategy {
void execute(SharedData sharedData);
}
工厂模式关注对象创建,策略模式关注行为封装。它们经常一起使用:
两者都用于封装算法,但:
结构相似但意图不同:
在我参与的一个电商平台项目中,我们使用策略模式重构了价格计算系统。原来的系统有大量if-else判断各种促销活动,维护困难。重构后:
重构后的优势:
经验分享:策略模式特别适合处理业务规则经常变化的场景。我们在双11前新增了5种促销策略,都通过新增策略类实现,没有修改任何现有代码。
虽然策略模式很灵活,但也需要注意性能问题:
对象创建开销:频繁创建策略对象会影响性能
方法调用开销:策略模式会增加一层间接调用
内存占用:大量策略类会增加内存使用
java复制// 使用枚举实现单例策略
public enum SingletonStrategy implements Strategy {
INSTANCE;
@Override
public void execute() {
// 实现逻辑
}
}
// 使用对象池
public class StrategyPool {
private final ObjectPool<ExpensiveStrategy> pool;
public StrategyPool() {
pool = new GenericObjectPool<>(new ExpensiveStrategyFactory());
}
public void executeWithStrategy() {
ExpensiveStrategy strategy = pool.borrowObject();
try {
strategy.execute();
} finally {
pool.returnObject(strategy);
}
}
}
策略模式的一个主要优势就是便于测试。测试时要注意:
java复制public class DiscountStrategyTest {
@Test
public void testRegularDiscount() {
DiscountStrategy strategy = new RegularDiscountStrategy();
assertEquals(100, strategy.apply(100), 0.001);
}
@Test
public void testVIPDiscount() {
DiscountStrategy strategy = new VIPDiscountStrategy();
assertEquals(90, strategy.apply(100), 0.001);
}
@Test
public void testContextWithStrategy() {
DiscountContext context = new DiscountContext(new VIPDiscountStrategy());
assertEquals(90, context.execute(100), 0.001);
}
}
虽然策略模式很强大,但并不是所有情况都适用:
在这些情况下,简单的条件语句可能更合适。记住:设计模式是工具,不是目标。