设计模式-策略模式:从鞋厂促销活动看算法的灵活切换
在商业竞争中,灵活的营销策略是企业抢占市场的关键。比如一家鞋厂,在不同场景下会采用不同的促销方式:日常满减、节假日打折、会员双倍积分……如果每次换促销活动都要大改系统,不仅效率低下,还容易出错。
软件开发中也存在类似需求:同一业务目标(如计算价格、排序数据)有多种实现算法,且需要根据场景动态切换。策略模式(Strategy Pattern)正是为这种“算法族动态切换”设计的模式,它将不同算法封装成独立策略,让算法的选择与使用彻底解耦。
本文将以鞋厂的促销活动为实例,详解策略模式的原理、实现及实战价值。
一、为什么需要策略模式?——从混乱的促销代码说起
假设鞋厂最初的促销系统是这样实现的:根据促销类型用if-else
判断逻辑,计算最终价格:
// 订单类(包含混乱的促销逻辑)
public class Order {private double price; // 原价private String promotionType; // 促销类型:"fullReduce"(满减)、"discount"(打折)、"points"(积分)public Order(double price, String promotionType) {this.price = price;this.promotionType = promotionType;}// 计算最终价格(包含所有促销逻辑,混乱不堪)public double calculateFinalPrice() {if ("fullReduce".equals(promotionType)) {// 满减规则:满300减50,满500减100if (price >= 500) {return price - 100;} else if (price >= 300) {return price - 50;} else {return price;}} else if ("discount".equals(promotionType)) {// 打折规则:全场8折return price * 0.8;} else if ("points".equals(promotionType)) {// 积分规则:不降价,但赠送双倍积分System.out.println("赠送" + (int)(price * 2) + "积分");return price;} else {// 默认无促销return price;}}
}// 客户端调用
public class Client {public static void main(String[] args) {Order order1 = new Order(600, "fullReduce");System.out.println("满减后价格:" + order1.calculateFinalPrice());Order order2 = new Order(600, "discount");System.out.println("打折后价格:" + order2.calculateFinalPrice());}
}
这种实现方式存在三个严重问题:
- 耦合度高:促销算法与订单类深度绑定,新增或修改促销方式必须修改
Order
类,违反“开闭原则”。 - 可读性差:所有算法逻辑堆砌在
calculateFinalPrice
方法中,随着促销方式增多,代码会变得臃肿不堪。 - 扩展性差:如果需要临时新增“限时秒杀”活动,必须侵入原有代码,容易引发其他促销逻辑的bug。
策略模式的解决方案是:将每种促销算法封装成独立“策略”类,订单类只需根据场景选择对应的策略,无需关心算法细节。就像鞋厂的促销活动,每种活动有独立的规则文档,店员只需按规则执行即可。
二、策略模式的核心结构
策略模式通过四个核心角色实现“算法的封装与切换”:
- 抽象策略(Strategy):定义所有具体策略的公共接口(如“计算促销后价格”的方法)。
- 具体策略(Concrete Strategy):实现抽象策略接口,封装具体的算法(如满减、打折、积分等促销规则)。
- 环境类(Context):持有一个策略对象的引用,负责策略的选择与使用(如订单类根据促销类型选择对应的策略)。
- 客户端(Client):创建具体策略对象,并设置到环境类中(如根据用户选择的促销活动,为订单指定策略)。
用一句话概括:环境类通过调用抽象策略接口使用算法,具体使用哪种算法由客户端决定,且可以动态切换。
三、策略模式实战:鞋厂的灵活促销系统
场景模拟
鞋厂需要支持多种促销活动,且能随时新增或停用活动:
- 满减策略:满300减50,满500减100,满1000减300。
- 打折策略:平日9折,节假日8折(可动态调整折扣率)。
- 积分策略:不降价,但消费1元赠送2积分(积分可兑换商品)。
- 组合策略:未来可能新增“满减+积分”的组合活动。
要求系统能根据不同时间、不同用户群体自动切换策略,且新增策略时无需修改原有代码。
代码实现
// 1. 抽象策略:促销策略接口
public interface PromotionStrategy {// 计算促销后的价格double calculate(double originalPrice);// 获取策略描述String getDescription();
}// 2. 具体策略1:满减策略
public class FullReduceStrategy implements PromotionStrategy {@Overridepublic double calculate(double originalPrice) {if (originalPrice >= 1000) {return originalPrice - 300;} else if (originalPrice >= 500) {return originalPrice - 100;} else if (originalPrice >= 300) {return originalPrice - 50;} else {return originalPrice;}}@Overridepublic String getDescription() {return "满减促销:满300减50,满500减100,满1000减300";}
}// 2. 具体策略2:打折策略(支持动态调整折扣率)
public class DiscountStrategy implements PromotionStrategy {private double discountRate; // 折扣率(如0.8表示8折)public DiscountStrategy(double discountRate) {this.discountRate = discountRate;}@Overridepublic double calculate(double originalPrice) {return originalPrice * discountRate;}@Overridepublic String getDescription() {return String.format("打折促销:%.1f折", discountRate * 10);}
}// 2. 具体策略3:积分策略
public class PointsStrategy implements PromotionStrategy {@Overridepublic double calculate(double originalPrice) {// 不改变价格,但赠送积分int points = (int) (originalPrice * 2);System.out.println("赠送" + points + "积分(1积分=0.01元,可兑换商品)");return originalPrice;}@Overridepublic String getDescription() {return "积分促销:消费1元赠送2积分";}
}// 3. 环境类:订单(使用策略计算价格)
public class Order {private double originalPrice; // 原价private PromotionStrategy strategy; // 促销策略// 构造器:传入原价和策略public Order(double originalPrice, PromotionStrategy strategy) {this.originalPrice = originalPrice;this.strategy = strategy;}// 动态切换策略(支持订单创建后更换促销方式)public void setStrategy(PromotionStrategy strategy) {this.strategy = strategy;}// 计算最终价格(委托给策略处理)public double getFinalPrice() {System.out.println("促销方式:" + strategy.getDescription());return strategy.calculate(originalPrice);}
}// 4. 客户端:根据场景选择策略
public class ShoeStore {public static void main(String[] args) {// 场景1:日常满减活动PromotionStrategy fullReduce = new FullReduceStrategy();Order order1 = new Order(600, fullReduce);System.out.println("订单1最终价格:" + order1.getFinalPrice() + "\n");// 场景2:节假日8折活动PromotionStrategy holidayDiscount = new DiscountStrategy(0.8);Order order2 = new Order(600, holidayDiscount);System.out.println("订单2最终价格:" + order2.getFinalPrice() + "\n");// 场景3:会员日积分活动PromotionStrategy points = new PointsStrategy();Order order3 = new Order(600, points);System.out.println("订单3最终价格:" + order3.getFinalPrice() + "\n");// 场景4:动态切换策略(订单创建后改参与打折)order1.setStrategy(new DiscountStrategy(0.9));System.out.println("订单1更换策略后价格:" + order1.getFinalPrice());}
}
运行结果
促销方式:满减促销:满300减50,满500减100,满1000减300
订单1最终价格:500.0促销方式:打折促销:8.0折
订单2最终价格:480.0促销方式:积分促销:消费1元赠送2积分
赠送1200积分(1积分=0.01元,可兑换商品)
订单3最终价格:600.0促销方式:打折促销:9.0折
订单1更换策略后价格:540.0
四、策略模式的灵活性:动态扩展与组合
策略模式的最大优势是支持算法的动态扩展和灵活组合,无需修改现有代码。
场景扩展1:新增组合策略
如果鞋厂推出“满减+积分”的组合促销(满减后再送积分),只需新增一个策略类:
// 新增具体策略:满减+积分组合策略
public class FullReducePlusPointsStrategy implements PromotionStrategy {// 组合两个基础策略private PromotionStrategy fullReduce = new FullReduceStrategy();private PromotionStrategy points = new PointsStrategy();@Overridepublic double calculate(double originalPrice) {// 先计算满减后的价格double priceAfterReduce = fullReduce.calculate(originalPrice);// 再对满减后的价格计算积分(积分策略会打印赠送信息)points.calculate(priceAfterReduce);return priceAfterReduce;}@Overridepublic String getDescription() {return "满减+积分组合促销:满减后再送积分";}
}// 客户端直接使用新策略
Order order4 = new Order(600, new FullReducePlusPointsStrategy());
System.out.println("订单4最终价格:" + order4.getFinalPrice());
// 输出:
// 促销方式:满减+积分组合促销:满减后再送积分
// 赠送1000积分(1积分=0.01元,可兑换商品)
// 订单4最终价格:500.0
场景扩展2:策略工厂管理策略
当策略较多时,可通过“策略工厂”统一管理,避免客户端直接依赖具体策略:
// 策略工厂:根据类型创建策略
public class PromotionStrategyFactory {// 缓存策略实例(避免重复创建)private static Map<String, PromotionStrategy> strategies = new HashMap<>();static {// 初始化策略strategies.put("fullReduce", new FullReduceStrategy());strategies.put("discount8", new DiscountStrategy(0.8));strategies.put("points", new PointsStrategy());}// 根据类型获取策略public static PromotionStrategy getStrategy(String type) {return strategies.getOrDefault(type, new FullReduceStrategy()); // 默认满减}
}// 客户端通过工厂获取策略,无需知道具体实现类
Order order5 = new Order(600, PromotionStrategyFactory.getStrategy("discount8"));
五、策略模式的常见应用场景
从鞋厂促销系统出发,我们可以延伸出策略模式在软件开发中的典型应用:
1. 支付方式选择
电商平台的支付系统支持多种支付方式(支付宝、微信、银联),每种支付方式的接口调用和加密逻辑不同,可用策略模式封装:
- 抽象策略:
PaymentStrategy
(定义pay
方法)。 - 具体策略:
AlipayStrategy
、WechatPayStrategy
。 - 环境类:
OrderPayment
(根据用户选择的支付方式调用对应策略)。
2. 排序算法切换
集合排序可根据数据规模选择不同算法(冒泡排序、快速排序、归并排序):
- 抽象策略:
SortStrategy
(定义sort
方法)。 - 具体策略:
BubbleSort
、QuickSort
。 - 环境类:
DataProcessor
(根据数据量自动选择策略)。
3. 日志输出方式
日志系统可选择不同输出目标(控制台、文件、数据库):
- 抽象策略:
LogStrategy
(定义log
方法)。 - 具体策略:
ConsoleLog
、FileLog
。 - 环境类:
Logger
(根据配置切换输出策略)。
4. 游戏角色技能系统
游戏中角色的技能有不同释放逻辑(物理攻击、魔法攻击、治疗):
- 抽象策略:
SkillStrategy
(定义cast
方法)。 - 具体策略:
PhysicalAttack
、MagicAttack
。 - 环境类:
Role
(根据玩家操作切换技能策略)。
六、策略模式的优缺点
优点
- 解耦算法与使用:算法的实现与使用分离,环境类无需知道算法细节(如订单类不用关心满减规则)。
- 灵活扩展:新增算法只需添加新策略类,无需修改现有代码(符合“开闭原则”)。
- 动态切换:支持运行时动态更换算法(如订单创建后切换促销方式)。
- 避免多重条件判断:用策略选择替代
if-else
或switch
,代码更清晰。
缺点
- 策略类增多:每种算法对应一个策略类,可能导致类数量增加(可通过策略工厂管理)。
- 客户端需了解策略:客户端必须知道所有策略的存在才能选择合适的策略(可通过工厂模式封装选择逻辑)。
- 策略间无法共享状态:如果多个策略需要共享数据(如促销活动的起止时间),需额外处理(如将共享数据放入环境类)。
七、策略模式与工厂模式的结合
策略模式解决“算法封装与切换”,工厂模式解决“对象创建”,两者结合可进一步降低客户端复杂度:
- 工厂模式负责创建策略对象(隐藏策略的实例化细节)。
- 策略模式负责算法的执行与切换。
就像鞋厂的“促销活动管理系统”:
- 工厂(
PromotionStrategyFactory
)负责根据活动类型创建对应的策略对象。 - 策略(
FullReduceStrategy
等)负责执行具体的促销计算。 - 客户端只需告诉工厂“要哪种活动”,无需直接创建策略。
总结:从促销活动到代码设计的启示
策略模式就像鞋厂的促销活动管理:
- 每种促销方式(具体策略)是独立的规则,可单独设计、修改、停用。
- 订单系统(环境类)只需按规则计算价格,不关心规则细节。
- 客户(客户端)可以自由选择活动,系统能动态响应选择。
在代码中,当你遇到“多种算法实现同一目标,且需要动态切换”的场景(如支付方式、排序算法、日志输出),策略模式能让你的代码更清晰、更灵活、更易扩展。
Studying will never be ending.
▲如有纰漏,烦请指正~~