第一次接触策略模式时,我也被那些抽象的定义搞得一头雾水。直到有天在奶茶店点单,才突然开窍——这不就是策略模式的完美案例吗?店员不会因为你要"少糖去冰"就重新培训,他们只是根据不同的选择组合执行对应的制作流程。这种"选择-执行"的分离,正是策略模式的精髓所在。
在Java开发中,策略模式属于行为型设计模式,它定义了一系列算法(策略),并将每个算法封装起来,使它们可以相互替换。最大的特点是算法的变化独立于使用算法的客户。就像奶茶店可以随时新增"零卡糖"选项而不影响其他配方,我们的代码也能灵活扩展新策略而不改动原有逻辑。
提示:策略模式特别适合处理那些业务规则频繁变更的场景,比如电商促销活动、支付方式选择、日志记录策略等。当你的代码中出现大量条件判断语句时,就是考虑策略模式的好时机。
工欲善其事,必先利其器。虽然策略模式本身不依赖特定工具链,但合理的环境配置能事半功倍。我推荐以下组合:
验证环境是否就绪:
bash复制java -version # 应该看到类似"17.0.2"的输出
mvn -v # 确认Maven版本≥3.6.0
清晰的包结构能让策略模式的优势更加凸显。建议采用如下Maven项目结构:
code复制src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── yourpackage/
│ │ ├── strategy/ # 策略接口和实现
│ │ ├── context/ # 策略上下文
│ │ └── App.java # 主程序
│ └── resources/ # 配置文件
└── test/ # 单元测试
注意:避免把所有策略类堆在一个文件中。每个策略类应该是独立的,这样修改或添加新策略时才不会引发冲突。
策略模式的第一步是抽象出可变行为。以电商折扣为例,我们定义一个顶层接口:
java复制public interface DiscountStrategy {
/**
* 计算最终价格
* @param originalPrice 商品原价
* @return 折后价格
*/
double applyDiscount(double originalPrice);
/**
* 策略唯一标识
* @return 策略名称
*/
String getStrategyName();
}
接口设计有两个关键点:
下面是三种典型折扣策略的实现:
满减策略:
java复制public class FullReductionStrategy implements DiscountStrategy {
private static final double THRESHOLD = 100.0;
private static final double REDUCTION = 10.0;
@Override
public double applyDiscount(double originalPrice) {
return originalPrice >= THRESHOLD ? originalPrice - REDUCTION : originalPrice;
}
@Override
public String getStrategyName() {
return "FULL_REDUCTION";
}
}
百分比折扣策略:
java复制public class PercentageDiscountStrategy implements DiscountStrategy {
private final double discountRate; // 折扣率
public PercentageDiscountStrategy(double discountRate) {
if (discountRate <= 0 || discountRate >= 1) {
throw new IllegalArgumentException("折扣率必须在0到1之间");
}
this.discountRate = discountRate;
}
@Override
public double applyDiscount(double originalPrice) {
return originalPrice * discountRate;
}
@Override
public String getStrategyName() {
return "PERCENTAGE_" + (int)(discountRate * 100) + "OFF";
}
}
新用户首单策略:
java复制public class NewUserDiscountStrategy implements DiscountStrategy {
@Override
public double applyDiscount(double originalPrice) {
return originalPrice * 0.8; // 新用户8折
}
@Override
public String getStrategyName() {
return "NEW_USER";
}
}
技巧:策略类应该是无状态的(stateless)。如果需要配置参数,应该通过构造器注入,而不是在方法调用时传入。这使得策略对象可以安全地复用。
Context类是连接客户端和策略的桥梁:
java复制public class DiscountContext {
private DiscountStrategy strategy;
public void setStrategy(DiscountStrategy strategy) {
Objects.requireNonNull(strategy, "策略不能为null");
this.strategy = strategy;
}
public double executeStrategy(double price) {
if (strategy == null) {
throw new IllegalStateException("未设置折扣策略");
}
return strategy.applyDiscount(price);
}
public String getCurrentStrategyName() {
return strategy != null ? strategy.getStrategyName() : "NONE";
}
}
关键设计要点:
java复制public class Client {
public static void main(String[] args) {
DiscountContext context = new DiscountContext();
// 满减策略
context.setStrategy(new FullReductionStrategy());
System.out.println("满减策略: " + context.executeStrategy(120.0));
// 切换为百分比折扣
context.setStrategy(new PercentageDiscountStrategy(0.9));
System.out.println("9折策略: " + context.executeStrategy(120.0));
// 新用户策略
context.setStrategy(new NewUserDiscountStrategy());
System.out.println("新用户策略: " + context.executeStrategy(120.0));
}
}
当策略较多时,可以使用工厂模式管理策略创建:
java复制public class DiscountStrategyFactory {
private static final Map<String, DiscountStrategy> strategies = Map.of(
"FULL_REDUCTION", new FullReductionStrategy(),
"PERCENTAGE_90", new PercentageDiscountStrategy(0.9),
"NEW_USER", new NewUserDiscountStrategy()
);
public static DiscountStrategy getStrategy(String type) {
DiscountStrategy strategy = strategies.get(type);
if (strategy == null) {
throw new IllegalArgumentException("未知策略类型: " + type);
}
return strategy;
}
public static Set<String> getAvailableStrategies() {
return strategies.keySet();
}
}
使用方式:
java复制context.setStrategy(DiscountStrategyFactory.getStrategy("PERCENTAGE_90"));
经验:工厂模式特别适合策略类型较多,或者策略创建过程复杂的情况。如果策略很少且简单,直接new也是可以的。
在Spring Boot项目中,可以利用依赖注入自动收集所有策略:
java复制@Service
public class DiscountService {
private final Map<String, DiscountStrategy> strategyMap;
@Autowired
public DiscountService(List<DiscountStrategy> strategies) {
this.strategyMap = strategies.stream()
.collect(Collectors.toMap(
DiscountStrategy::getStrategyName,
Function.identity()
));
}
public double applyDiscount(String strategyName, double price) {
DiscountStrategy strategy = strategyMap.get(strategyName);
if (strategy == null) {
throw new IllegalArgumentException("无效策略: " + strategyName);
}
return strategy.applyDiscount(price);
}
}
Java 8+中,简单策略可以用Lambda表达式实现:
java复制Map<String, DiscountStrategy> lambdaStrategies = Map.of(
"VIP", price -> price * 0.85,
"HOLIDAY", price -> price > 200 ? price - 50 : price
);
适用场景:
复杂业务可能需要组合多个策略:
java复制public class CombinedDiscountStrategy implements DiscountStrategy {
private final List<DiscountStrategy> strategies;
public CombinedDiscountStrategy(DiscountStrategy... strategies) {
this.strategies = List.of(strategies);
}
@Override
public double applyDiscount(double originalPrice) {
double currentPrice = originalPrice;
for (DiscountStrategy strategy : strategies) {
currentPrice = strategy.applyDiscount(currentPrice);
}
return currentPrice;
}
// 其他方法实现...
}
使用示例:
java复制DiscountStrategy combined = new CombinedDiscountStrategy(
new FullReductionStrategy(),
new PercentageDiscountStrategy(0.9)
);
频繁创建策略对象可能带来性能问题。解决方案:
java复制public enum SingletonStrategies {
FULL_REDUCTION(new FullReductionStrategy()),
PERCENTAGE_90(new PercentageDiscountStrategy(0.9));
private final DiscountStrategy strategy;
SingletonStrategies(DiscountStrategy strategy) {
this.strategy = strategy;
}
public DiscountStrategy getStrategy() {
return strategy;
}
}
警告:避免在策略类中使用静态变量,除非你非常清楚并发场景下的行为。
策略模式的一个巨大优势是便于单元测试。测试要点:
示例测试:
java复制class FullReductionStrategyTest {
@Test
void shouldReduceWhenOverThreshold() {
DiscountStrategy strategy = new FullReductionStrategy();
assertEquals(90.0, strategy.applyDiscount(100.0), 0.001);
}
@Test
void shouldNotReduceWhenUnderThreshold() {
DiscountStrategy strategy = new FullReductionStrategy();
assertEquals(99.0, strategy.applyDiscount(99.0), 0.001);
}
}
两者经常结合使用,如前面的DiscountStrategyFactory示例。
状态模式可以看作策略模式的变体,其中策略切换由内部状态驱动。
模板方法使用继承,策略模式使用组合。
在电商系统中,我们曾用策略模式重构了复杂的促销引擎。原始代码有3000多行的if-else,维护起来简直是噩梦。重构后:
效果:
遇到的坑:
经验:在设计策略接口时,一定要考虑持久化和远程调用的需求。简单的做法是给每个策略设计唯一标识符。
反模式警告:
通过配置文件动态调整策略参数:
yaml复制discount:
strategies:
fullReduction:
threshold: 100.0
reduction: 10.0
percentage:
rate: 0.9
创建流畅API来组合策略:
java复制DiscountStrategy strategy = DiscountBuilder.create()
.first(new FullReductionStrategy())
.then(new PercentageDiscountStrategy(0.9))
.build();
使用Java的Instrumentation API或Spring Cloud的RefreshScope实现运行时策略更新。
Q1:什么时候不该用策略模式?
A:当算法很少变化、或者条件逻辑非常简单时。比如只有2-3个固定分支的if-else,引入策略模式反而会增加复杂度。
Q2:策略类需要设计成单例吗?
A:如果策略是无状态的,单例可以节省内存;有状态策略则需要根据场景决定。Spring中通常用@Scope("prototype")管理有状态bean。
Q3:如何处理策略执行失败的情况?
A:可以在策略接口中定义统一的异常类型,或者返回包含详细结果的策略执行结果对象。
Q4:策略模式会导致类爆炸吗?
A:确实有这个风险。对于简单策略,可以使用Lambda或方法引用;也可以将相关策略组织在同一个类中,通过内部类实现。
Q5:如何选择策略?
A:常见方式包括:配置文件、数据库配置、运行时决策算法等。复杂的策略选择可以单独抽象为"策略选择器"。
让我们看一个重构前的代码片段:
java复制public double calculatePrice(User user, Product product) {
if (user.isVip()) {
if (product.getCategory().equals("ELECTRONICS")) {
return product.getPrice() * 0.85;
} else {
return product.getPrice() * 0.9;
}
} else if (user.isNewUser()) {
return product.getPrice() * 0.95;
} else if (product.isOnSale()) {
return product.getPrice() - 10;
}
return product.getPrice();
}
重构步骤:
java复制public interface PriceCalculationStrategy {
boolean supports(User user, Product product);
double calculate(User user, Product product);
}
java复制public class VipElectronicsStrategy implements PriceCalculationStrategy {
@Override
public boolean supports(User user, Product product) {
return user.isVip() && product.getCategory().equals("ELECTRONICS");
}
@Override
public double calculate(User user, Product product) {
return product.getPrice() * 0.85;
}
}
// 其他策略类似...
java复制public class PriceCalculator {
private final List<PriceCalculationStrategy> strategies;
public PriceCalculator(List<PriceCalculationStrategy> strategies) {
this.strategies = strategies;
}
public double calculate(User user, Product product) {
return strategies.stream()
.filter(s -> s.supports(user, product))
.findFirst()
.map(s -> s.calculate(user, product))
.orElse(product.getPrice());
}
}
重构后的优势:
良好的策略模式实现应该具备高可测试性。测试要点:
java复制public interface DiscountStrategyTest<T extends DiscountStrategy> {
T createStrategy();
@Test
default void shouldReturnSameOrLowerPrice() {
T strategy = createStrategy();
double original = 100.0;
double discounted = strategy.applyDiscount(original);
assertTrue(discounted <= original,
"折扣后价格不应高于原价");
}
}
java复制class FullReductionStrategyTest implements DiscountStrategyTest<FullReductionStrategy> {
@Override
public FullReductionStrategy createStrategy() {
return new FullReductionStrategy();
}
@Test
void shouldApplyReductionWhenOverThreshold() {
FullReductionStrategy strategy = createStrategy();
assertEquals(90.0, strategy.applyDiscount(100.0));
}
}
java复制class DiscountContextTest {
@Test
void shouldUseCurrentStrategy() {
DiscountContext context = new DiscountContext();
context.setStrategy(new FullReductionStrategy());
assertEquals(90.0, context.executeStrategy(100.0));
}
@Test
void shouldThrowWhenStrategyNotSet() {
DiscountContext context = new DiscountContext();
assertThrows(IllegalStateException.class,
() -> context.executeStrategy(100.0));
}
}
java复制@SpringBootTest
class DiscountServiceIntegrationTest {
@Autowired
private DiscountService discountService;
@Test
void shouldApplyConfiguredStrategies() {
double price = discountService.applyDiscount("FULL_REDUCTION", 100.0);
assertEquals(90.0, price);
}
}
虽然策略模式提供了良好的设计,但也需要考虑性能影响:
对象创建开销:频繁创建策略实例会影响性能
方法调用开销:多一层间接调用
内存占用:大量策略类会增加PermGen/Metaspace使用
基准测试示例(JMH):
java复制@BenchmarkMode(Mode.Throughput)
@State(Scope.Benchmark)
public class StrategyPerformanceBenchmark {
private DiscountStrategy strategy;
private DiscountContext context;
@Setup
public void setup() {
strategy = new FullReductionStrategy();
context = new DiscountContext();
context.setStrategy(strategy);
}
@Benchmark
public double directCall() {
return strategy.applyDiscount(100.0);
}
@Benchmark
public double throughContext() {
return context.executeStrategy(100.0);
}
}
典型结果:
策略泄露:上下文暴露了策略内部细节
过度抽象:为不太可能变化的逻辑创建策略
策略膨胀:一个策略类变得过于复杂
循环依赖:策略之间相互调用
忽略null策略:没有处理策略未设置的情况
利用Spring的依赖注入管理策略:
java复制@Configuration
public class StrategyConfig {
@Bean
@ConditionalOnProperty(name = "discount.strategy.full-reduction.enabled", havingValue = "true")
public DiscountStrategy fullReductionStrategy() {
return new FullReductionStrategy();
}
// 其他策略bean...
}
为策略添加横切关注点:
java复制public class LoggingStrategyProxy implements InvocationHandler {
private final DiscountStrategy target;
public static DiscountStrategy create(DiscountStrategy target) {
return (DiscountStrategy) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LoggingStrategyProxy(target)
);
}
private LoggingStrategyProxy(DiscountStrategy target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
long duration = System.currentTimeMillis() - start;
System.out.printf("策略%s执行耗时%dms%n",
target.getStrategyName(), duration);
return result;
}
}
Java 8+中可以用函数表示简单策略:
java复制public class FunctionalDiscountContext {
private Function<Double, Double> strategy;
public void setStrategy(Function<Double, Double> strategy) {
this.strategy = strategy;
}
public double execute(double price) {
return strategy.apply(price);
}
}
使用方式:
java复制context.setStrategy(price -> price > 100 ? price - 10 : price);
在大型系统中,策略模式可以应用于:
微服务中的策略模式示例:
java复制@FeignClient(name = "payment-service")
public interface PaymentStrategy {
@PostMapping("/pay")
PaymentResult pay(@RequestBody PaymentRequest request);
}
// 具体策略
@Service
@RequiredArgsConstructor
public class AlipayStrategy implements PaymentStrategy {
private final AlipayClient alipayClient;
@Override
public PaymentResult pay(PaymentRequest request) {
// 支付宝具体实现
}
}
// 上下文服务
@Service
public class PaymentService {
private final Map<String, PaymentStrategy> strategies;
public PaymentService(List<PaymentStrategy> strategyList) {
this.strategies = strategyList.stream()
.collect(Collectors.toMap(
s -> s.getClass().getSimpleName(),
Function.identity()
));
}
public PaymentResult processPayment(String provider, PaymentRequest request) {
PaymentStrategy strategy = strategies.get(provider + "Strategy");
if (strategy == null) {
throw new IllegalArgumentException("不支持的支付提供商");
}
return strategy.pay(request);
}
}
当策略模式出现问题时,可以检查:
策略未生效:
性能问题:
内存泄漏:
调试技巧:
对于高度规范的策略模式实现,可以考虑:
java复制@Strategy(pattern = ".*Discount")
public class PercentageDiscountStrategyImpl implements DiscountStrategy {
// 自动生成代码
}
java复制@RequiredArgsConstructor
public abstract class AbstractDiscountStrategy implements DiscountStrategy {
private final String strategyName;
@Override
public String getStrategyName() {
return strategyName;
}
}
随着业务发展,策略模式实现可能需要调整:
java复制public interface DiscountStrategy {
String getVersion();
// 其他方法...
}
java复制public class StrategyDescriptor {
private String name;
private String description;
private String author;
private LocalDate createdDate;
// getters/setters...
}
java复制public interface ManageableStrategy extends DiscountStrategy {
void init();
void destroy();
boolean isEnabled();
}
重构路径建议:
策略模式在其他语言中的实现特点:
Python:
python复制def full_reduction_strategy(price):
return price - 10 if price >= 100 else price
context.strategy = full_reduction_strategy
JavaScript:
javascript复制const strategies = {
fullReduction: price => price >= 100 ? price - 10 : price,
percentage: price => price * 0.9
};
context.execute = function(strategyName, price) {
return strategies[strategyName](price);
};
Go:
go复制type DiscountStrategy interface {
Apply(float64) float64
}
type fullReduction struct{}
func (s fullReduction) Apply(price float64) float64 {
if price >= 100 {
return price - 10
}
return price
}
在DDD中,策略模式常用于:
示例:运费计算策略
java复制public interface ShippingCostStrategy {
Money calculate(Order order);
}
public class StandardShipping implements ShippingCostStrategy {
public Money calculate(Order order) {
// 标准计算逻辑
}
}
public class ExpeditedShipping implements ShippingCostStrategy {
public Money calculate(Order order) {
// 加急计算逻辑
}
}
策略模式体现了以下SOLID原则:
策略膨胀:创建过多细粒度策略
上下文过载:上下文承担太多职责
策略耦合:策略之间直接调用
过度设计:为简单变化引入策略模式
Spring示例:
java复制@Configuration
public class StrategyConfiguration {
@Bean
@ConditionalOnExpression("#{'${discount.strategy.type}' == 'percentage'}")
public DiscountStrategy percentageStrategy() {
return new PercentageDiscountStrategy(0.9);
}
}
良好的文档能极大提升策略模式的可维护性:
JavaDoc示例:
java复制/**
* 折扣策略接口,用于计算商品折扣后价格。
*
* <p>实现类应该:
* <ul>
* <li>保证幂等性:相同输入产生相同输出</li>
* <li>线程安全:无状态或妥善管理状态</li>
* <li>提供有意义的策略名称</li>
* </ul>
*
* @see FullReductionStrategy 满减策略实现
* @see PercentageDiscountStrategy 百分比折扣策略
*/
public interface DiscountStrategy {
// ...
}
生产环境中策略模式的监控要点:
Spring Boot Actuator示例:
java复制@RestController
public class StrategyMetricsController {
private final MeterRegistry meterRegistry;
private final DiscountService discountService;
@GetMapping("/metrics/strategies")
public Map<String, Object> getStrategyMetrics() {
Map<String, Object> metrics = new HashMap<>();
metrics.put("activeStrategies", discountService.getStrategyCount());
metrics.put("executionCount",
meterRegistry.counter("strategy.executions").count());
return metrics;
}
}
策略模式的安全注意事项:
策略注入:防止恶意策略实现
敏感操作:策略可能执行特权操作
配置安全:保护策略配置数据
审计日志:记录关键策略操作
多人协作开发策略模式时:
在实际项目中应用策略模式多年,我有几点深刻体会:
适度抽象:不是所有if-else都需要策略模式,但遇到频繁变更的业务规则时,策略模式能显著提升可维护性。
命名至关重要:策略类和方法的命名要能直观反映其目的,如ExpressShippingStrategy比StrategyImplA要好得多。
测试友好性:策略模式让单元测试变得简单,每个策略可以独立测试,这是提高代码质量的重要保障。
性能平衡:虽然策略模式引入了一定间接性,但在大多数业务场景中,这种开销远小于其带来的架构收益。
组合威力:策略模式与其他模式(如工厂、装饰器)组合使用时,能解决更复杂的业务问题。
最成功的案例是在一个金融项目中,我们使用策略模式管理不同的手续费计算规则。当监管要求变化时,我们能快速添加新策略而不影响现有逻辑,这在频繁变化的金融领域尤为重要。
最大的教训是在早期项目中忽略了策略的生命周期管理,导致内存泄漏。现在我会特别注意:
策略模式就像瑞士军刀中的不同工具,关键在于识别何时该用哪个"工具"。随着经验积累,你会越来越擅长做出这种判断。