如何设计灵活且可扩展的促销系统:策略模式的电商应用实例 !! Java设计模式必知必会

1. Java 策略模式模式

策略模式是一种行为型设计模式,它就像是一个可以随时更换的工具箱。想象一下,您是一名厨师,面对不同的食材需要使用不同的切菜工具:

  1. 切肉需要用到菜刀
  2. 切面团需要用到面刀
  3. 切菜需要用到水果刀

在策略模式中:

  • 环境类(Context):相当于厨师本人,可以根据需要拿起不同的刀具
  • 策略接口(Strategy):相当于所有刀具的统一规范,都有"切东西"的功能
  • 具体策略(ConcreteStrategy):相当于具体的刀具,各自有特定的切东西方式

让我用Java代码来展示这个厨房的例子:

// 策略接口 - 所有刀具的共同规范
interface CuttingStrategy {
    void cut(String food);
}

// 具体策略 - 菜刀
class MeatKnife implements CuttingStrategy {
    @Override
    public void cut(String food) {
        System.out.println("用菜刀切" + food + ",力量大,切得断骨头");
    }
}

// 具体策略 - 面刀
class DoughKnife implements CuttingStrategy {
    @Override
    public void cut(String food) {
        System.out.println("用面刀切" + food + ",宽大平滑,切面团不粘刀");
    }
}

// 具体策略 - 水果刀
class FruitKnife implements CuttingStrategy {
    @Override
    public void cut(String food) {
        System.out.println("用水果刀切" + food + ",小巧灵活,适合精细操作");
    }
}

// 环境类 - 厨师
class Chef {
    private CuttingStrategy knife;
    
    // 换刀
    public void setKnife(CuttingStrategy knife) {
        this.knife = knife;
    }
    
    // 切食材
    public void cutFood(String food) {
        if (knife == null) {
            System.out.println("请先选择一把刀!");
            return;
        }
        knife.cut(food);
    }
}

// 测试
public class StrategyPatternDemo {
    public static void main(String[] args) {
        Chef chef = new Chef();
        
        // 切肉
        chef.setKnife(new MeatKnife());
        chef.cutFood("牛肉");
        
        // 切面团
        chef.setKnife(new DoughKnife());
        chef.cutFood("面团");
        
        // 切水果
        chef.setKnife(new FruitKnife());
        chef.cutFood("苹果");
    }
}

策略模式的优点:

  • 厨师(Context)不需要知道每把刀(Strategy)内部是如何工作的,只需要知道这把刀能切东西
  • 我们可以随时给厨师添加新的刀具(新的策略),比如添加一把剪刀,而不需要修改厨师类
  • 厨师可以根据需要灵活切换不同的刀具(策略)

这就是策略模式的精髓:将变化的部分(各种切东西的方法)封装起来,使它们可以互相替换,同时保持接口的统一,让使用者(厨师)能够根据不同情况选择不同的策略。

2. 策略模式综合案例:电商促销系统

1. 需求分析

背景

某电商平台需要实现一个灵活的促销系统,能够针对不同用户、不同时期应用不同的促销策略:

  1. 普通折扣:对商品直接打折,如8折、7折等
  2. 满减优惠:消费满一定金额后减免一定金额,如"满300减50"
  3. 会员折扣:根据会员等级提供额外折扣,如金卡会员9折,钻石会员8折
  4. 限时折扣:特定时间内享受更大折扣,如"双11全场5折"
  5. 第二件半价:购买同商品第二件半价

系统需要能够灵活切换这些促销策略,甚至组合使用,而不需要修改现有代码。

功能需求

  1. 能够针对订单应用不同的促销策略
  2. 能够动态切换和更新促销策略
  3. 支持未来扩展新的促销策略
  4. 提供清晰的接口供其他系统调用

2. 代码构建思路

我们将使用策略模式来实现这个促销系统:

  1. 创建一个促销策略接口PromotionStrategy,定义计算折扣的方法
  2. 为每种促销类型实现具体的策略类
  3. 使用策略工厂PromotionStrategyFactory来管理和获取策略实例
  4. 创建上下文类PromotionContext处理策略的应用
  5. 支持策略组合,实现复合策略CompositePromotionStrategy

3. 完整代码实现

// 1. 定义促销策略接口
package com.ecommerce.promotion;

import java.math.BigDecimal;

public interface PromotionStrategy {
    /**
     * 计算促销后的价格
     * @param originalPrice 原始价格
     * @param order 订单信息
     * @return 促销后的价格
     */
    BigDecimal calculatePrice(BigDecimal originalPrice, Order order);
    
    /**
     * 获取促销描述
     * @return 促销活动的描述
     */
    String getDescription();
}

// 2. 订单类
package com.ecommerce.promotion;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class Order {
    private String orderId;
    private Long userId;
    private String userLevel; // 普通用户、金卡会员、钻石会员等
    private List<OrderItem> items;
    private Date createTime;
    
    public Order(String orderId, Long userId, String userLevel) {
        this.orderId = orderId;
        this.userId = userId;
        this.userLevel = userLevel;
        this.items = new ArrayList<>();
        this.createTime = new Date();
    }
    
    public void addItem(OrderItem item) {
        items.add(item);
    }
    
    public BigDecimal calculateTotalPrice() {
        BigDecimal total = BigDecimal.ZERO;
        for (OrderItem item : items) {
            total = total.add(item.getPrice().multiply(new BigDecimal(item.getQuantity())));
        }
        return total;
    }
    
    // Getters and Setters
    public String getOrderId() {
        return orderId;
    }
    
    public Long getUserId() {
        return userId;
    }
    
    public String getUserLevel() {
        return userLevel;
    }
    
    public List<OrderItem> getItems() {
        return items;
    }
    
    public Date getCreateTime() {
        return createTime;
    }
}

// 3. 订单项类
package com.ecommerce.promotion;

import java.math.BigDecimal;

public class OrderItem {
    private String productId;
    private String productName;
    private BigDecimal price;
    private int quantity;
    private String category;
    
    public OrderItem(String productId, String productName, BigDecimal price, int quantity, String category) {
        this.productId = productId;
        this.productName = productName;
        this.price = price;
        this.quantity = quantity;
        this.category = category;
    }
    
    // Getters
    public String getProductId() {
        return productId;
    }
    
    public String getProductName() {
        return productName;
    }
    
    public BigDecimal getPrice() {
        return price;
    }
    
    public int getQuantity() {
        return quantity;
    }
    
    public String getCategory() {
        return category;
    }
}

// 4. 具体策略类实现

// 4.1 直接折扣策略
package com.ecommerce.promotion.strategies;

import com.ecommerce.promotion.Order;
import com.ecommerce.promotion.PromotionStrategy;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class PercentageDiscountStrategy implements PromotionStrategy {
    private BigDecimal discountPercentage;
    private String description;
    
    /**
     * @param discountPercentage 折扣百分比 (0.8 表示 8折)
     */
    public PercentageDiscountStrategy(BigDecimal discountPercentage, String description) {
        this.discountPercentage = discountPercentage;
        this.description = description;
    }
    
    @Override
    public BigDecimal calculatePrice(BigDecimal originalPrice, Order order) {
        return originalPrice.multiply(discountPercentage).setScale(2, RoundingMode.HALF_UP);
    }
    
    @Override
    public String getDescription() {
        return description;
    }
}

// 4.2 满减策略
package com.ecommerce.promotion.strategies;

import com.ecommerce.promotion.Order;
import com.ecommerce.promotion.PromotionStrategy;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class AmountDiscountStrategy implements PromotionStrategy {
    private BigDecimal threshold;
    private BigDecimal discountAmount;
    private String description;
    
    /**
     * @param threshold 满减阈值
     * @param discountAmount 减免金额
     */
    public AmountDiscountStrategy(BigDecimal threshold, BigDecimal discountAmount, String description) {
        this.threshold = threshold;
        this.discountAmount = discountAmount;
        this.description = description;
    }
    
    @Override
    public BigDecimal calculatePrice(BigDecimal originalPrice, Order order) {
        if (originalPrice.compareTo(threshold) >= 0) {
            return originalPrice.subtract(discountAmount).max(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP);
        }
        return originalPrice;
    }
    
    @Override
    public String getDescription() {
        return description;
    }
}

// 4.3 会员折扣策略
package com.ecommerce.promotion.strategies;

import com.ecommerce.promotion.Order;
import com.ecommerce.promotion.PromotionStrategy;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;

public class MemberDiscountStrategy implements PromotionStrategy {
    private Map<String, BigDecimal> levelDiscountMap;
    private String description;
    
    public MemberDiscountStrategy(String description) {
        this.levelDiscountMap = new HashMap<>();
        this.description = description;
        // 初始化各会员等级折扣
        levelDiscountMap.put("REGULAR", new BigDecimal("1.0"));  // 普通用户不打折
        levelDiscountMap.put("SILVER", new BigDecimal("0.95"));  // 银卡会员95折
        levelDiscountMap.put("GOLD", new BigDecimal("0.9"));     // 金卡会员9折
        levelDiscountMap.put("DIAMOND", new BigDecimal("0.8"));  // 钻石会员8折
    }
    
    @Override
    public BigDecimal calculatePrice(BigDecimal originalPrice, Order order) {
        String userLevel = order.getUserLevel();
        BigDecimal discount = levelDiscountMap.getOrDefault(userLevel, new BigDecimal("1.0"));
        return originalPrice.multiply(discount).setScale(2, RoundingMode.HALF_UP);
    }
    
    @Override
    public String getDescription() {
        return description;
    }
}

// 4.4 限时折扣策略
package com.ecommerce.promotion.strategies;

import com.ecommerce.promotion.Order;
import com.ecommerce.promotion.PromotionStrategy;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;

public class TimeDiscountStrategy implements PromotionStrategy {
    private Date startTime;
    private Date endTime;
    private BigDecimal discountPercentage;
    private String description;
    
    public TimeDiscountStrategy(Date startTime, Date endTime, 
                              BigDecimal discountPercentage, String description) {
        this.startTime = startTime;
        this.endTime = endTime;
        this.discountPercentage = discountPercentage;
        this.description = description;
    }
    
    @Override
    public BigDecimal calculatePrice(BigDecimal originalPrice, Order order) {
        Date orderTime = order.getCreateTime();
        if (orderTime.after(startTime) && orderTime.before(endTime)) {
            return originalPrice.multiply(discountPercentage).setScale(2, RoundingMode.HALF_UP);
        }
        return originalPrice;
    }
    
    @Override
    public String getDescription() {
        return description;
    }
}

// 4.5 第二件半价策略
package com.ecommerce.promotion.strategies;

import com.ecommerce.promotion.Order;
import com.ecommerce.promotion.OrderItem;
import com.ecommerce.promotion.PromotionStrategy;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;

public class SecondHalfPriceStrategy implements PromotionStrategy {
    private String description;
    
    public SecondHalfPriceStrategy(String description) {
        this.description = description;
    }
    
    @Override
    public BigDecimal calculatePrice(BigDecimal originalPrice, Order order) {
        Map<String, Integer> productCountMap = new HashMap<>();
        BigDecimal discount = BigDecimal.ZERO;
        
        // 统计每种商品的数量
        for (OrderItem item : order.getItems()) {
            String productId = item.getProductId();
            productCountMap.put(productId, productCountMap.getOrDefault(productId, 0) + item.getQuantity());
        }
        
        // 计算第二件半价的折扣金额
        for (OrderItem item : order.getItems()) {
            String productId = item.getProductId();
            int count = productCountMap.get(productId);
            if (count >= 2) {
                // 计算可以享受半价的商品数量
                int halfPriceCount = count / 2;
                // 每件商品半价的折扣金额
                BigDecimal itemDiscount = item.getPrice().divide(new BigDecimal("2"), 2, RoundingMode.HALF_UP);
                // 总折扣金额
                discount = discount.add(itemDiscount.multiply(new BigDecimal(halfPriceCount)));
            }
        }
        
        return originalPrice.subtract(discount).max(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP);
    }
    
    @Override
    public String getDescription() {
        return description;
    }
}

// 5. 复合策略 - 支持多个策略组合使用
package com.ecommerce.promotion.strategies;

import com.ecommerce.promotion.Order;
import com.ecommerce.promotion.PromotionStrategy;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

public class CompositePromotionStrategy implements PromotionStrategy {
    private List<PromotionStrategy> strategies;
    private String description;
    
    public CompositePromotionStrategy(String description) {
        this.strategies = new ArrayList<>();
        this.description = description;
    }
    
    public void addStrategy(PromotionStrategy strategy) {
        strategies.add(strategy);
    }
    
    @Override
    public BigDecimal calculatePrice(BigDecimal originalPrice, Order order) {
        BigDecimal finalPrice = originalPrice;
        for (PromotionStrategy strategy : strategies) {
            finalPrice = strategy.calculatePrice(finalPrice, order);
        }
        return finalPrice;
    }
    
    @Override
    public String getDescription() {
        StringBuilder sb = new StringBuilder(description + ": ");
        for (int i = 0; i < strategies.size(); i++) {
            sb.append(strategies.get(i).getDescription());
            if (i < strategies.size() - 1) {
                sb.append(" + ");
            }
        }
        return sb.toString();
    }
}

// 6. 促销策略工厂
package com.ecommerce.promotion;

import com.ecommerce.promotion.strategies.*;

import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class PromotionStrategyFactory {
    private static Map<String, PromotionStrategy> strategies = new HashMap<>();
    
    static {
        // 初始化预定义的促销策略
        strategies.put("NORMAL", new PercentageDiscountStrategy(new BigDecimal("1.0"), "正常价格"));
        strategies.put("DISCOUNT_80", new PercentageDiscountStrategy(new BigDecimal("0.8"), "8折优惠"));
        strategies.put("FULL_300_MINUS_50", new AmountDiscountStrategy(new BigDecimal("300"), new BigDecimal("50"), "满300减50"));
        strategies.put("MEMBER_DISCOUNT", new MemberDiscountStrategy("会员专属折扣"));
        strategies.put("SECOND_HALF", new SecondHalfPriceStrategy("第二件半价"));
        
        // 复合策略示例:会员额外8折 + 满300减50
        CompositePromotionStrategy composite = new CompositePromotionStrategy("会员专享活动");
        composite.addStrategy(new MemberDiscountStrategy("会员折扣"));
        composite.addStrategy(new AmountDiscountStrategy(new BigDecimal("300"), new BigDecimal("50"), "满减"));
        strategies.put("MEMBER_PLUS", composite);
    }
    
    public static PromotionStrategy getPromotionStrategy(String promotionKey) {
        return strategies.getOrDefault(promotionKey, strategies.get("NORMAL"));
    }
    
    // 注册新策略
    public static void registerStrategy(String promotionKey, PromotionStrategy strategy) {
        strategies.put(promotionKey, strategy);
    }
    
    // 创建限时折扣策略
    public static PromotionStrategy createTimeDiscountStrategy(Date startTime, Date endTime, 
                                                              BigDecimal discount, String description) {
        return new TimeDiscountStrategy(startTime, endTime, discount, description);
    }
    
    // 创建自定义折扣策略
    public static PromotionStrategy createPercentageDiscountStrategy(BigDecimal discount, String description) {
        return new PercentageDiscountStrategy(discount, description);
    }
    
    // 创建自定义满减策略
    public static PromotionStrategy createAmountDiscountStrategy(BigDecimal threshold, 
                                                               BigDecimal discountAmount, String description) {
        return new AmountDiscountStrategy(threshold, discountAmount, description);
    }
}

// 7. 促销上下文 - 负责应用策略
package com.ecommerce.promotion;

import java.math.BigDecimal;

public class PromotionContext {
    private PromotionStrategy strategy;
    
    public PromotionContext(PromotionStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void setStrategy(PromotionStrategy strategy) {
        this.strategy = strategy;
    }
    
    public BigDecimal executeStrategy(Order order) {
        BigDecimal originalPrice = order.calculateTotalPrice();
        return strategy.calculatePrice(originalPrice, order);
    }
    
    public String getPromotionDescription() {
        return strategy.getDescription();
    }
}

// 8. 客户端使用示例
package com.ecommerce.promotion;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class PromotionDemo {
    public static void main(String[] args) throws ParseException {
        // 创建订单
        Order order = new Order("ORD12345", 10001L, "GOLD");
        order.addItem(new OrderItem("P001", "笔记本电脑", new BigDecimal("5999"), 1, "电子产品"));
        order.addItem(new OrderItem("P002", "无线鼠标", new BigDecimal("99"), 2, "电子配件"));
        
        System.out.println("订单原价: " + order.calculateTotalPrice());
        
        // 使用8折促销策略
        PromotionContext context = new PromotionContext(
            PromotionStrategyFactory.getPromotionStrategy("DISCOUNT_80")
        );
        BigDecimal discountedPrice = context.executeStrategy(order);
        System.out.println(context.getPromotionDescription() + " 折后价: " + discountedPrice);
        
        // 切换到满减策略
        context.setStrategy(PromotionStrategyFactory.getPromotionStrategy("FULL_300_MINUS_50"));
        discountedPrice = context.executeStrategy(order);
        System.out.println(context.getPromotionDescription() + " 折后价: " + discountedPrice);
        
        // 切换到会员折扣策略
        context.setStrategy(PromotionStrategyFactory.getPromotionStrategy("MEMBER_DISCOUNT"));
        discountedPrice = context.executeStrategy(order);
        System.out.println(context.getPromotionDescription() + " 折后价: " + discountedPrice);
        
        // 使用第二件半价策略
        context.setStrategy(PromotionStrategyFactory.getPromotionStrategy("SECOND_HALF"));
        discountedPrice = context.executeStrategy(order);
        System.out.println(context.getPromotionDescription() + " 折后价: " + discountedPrice);
        
        // 使用复合促销策略
        context.setStrategy(PromotionStrategyFactory.getPromotionStrategy("MEMBER_PLUS"));
        discountedPrice = context.executeStrategy(order);
        System.out.println(context.getPromotionDescription() + " 折后价: " + discountedPrice);
        
        // 创建并使用限时折扣策略
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date startTime = sdf.parse("2024-03-20 00:00:00");
        Date endTime = sdf.parse("2024-03-25 23:59:59");
        
        PromotionStrategy timeStrategy = PromotionStrategyFactory.createTimeDiscountStrategy(
            startTime, endTime, new BigDecimal("0.5"), "限时五折"
        );
        context.setStrategy(timeStrategy);
        discountedPrice = context.executeStrategy(order);
        System.out.println(context.getPromotionDescription() + " 折后价: " + discountedPrice);
    }
}

设计亮点与优势

  1. 灵活性:可以轻松添加新的促销策略,无需修改现有代码,符合开闭原则
  2. 可扩展性:通过策略工厂和复合策略,支持新策略的动态添加和组合
  3. 可维护性:各个策略独立实现,职责单一,符合单一职责原则
  4. 易用性:提供了工厂方法和上下文类,使客户端代码简洁清晰

真实应用场景

这个策略模式案例可以应用于:

  1. 电商平台:不同商品类别应用不同促销策略
  2. 会员系统:根据会员等级提供不同折扣
  3. 营销活动:节日特惠、限时折扣等临时活动
  4. 支付系统:不同支付方式提供不同优惠

总结

通过策略模式,我们实现了一个灵活、可扩展的电商促销系统,能够满足多变的业务需求。这种设计避免了使用大量的条件判断语句,提高了代码的可维护性和可测试性,同时也为未来的功能扩展留出了空间。

如果您有任何问题或需要进一步的说明,请随时告诉我。

你可能感兴趣的:(策略模式,java,设计模式)