商城购买会员打折满减优惠券商品


前言

使用策略模式、工厂方法模式、单例模式实现一些购买策略,需求:商城商品根据用户是否是会员,如果是会员则根据会员等级进行商品原价折扣,再根据商品是否参加满减,如果参加满减则对同一类型满减的商品进行合计去掉满减金额,然后再根据用户是否使用优惠券,在进行则扣。


一、代码结构

在这里插入图片描述


二、UML图

在这里插入图片描述


三、代码实现

3.1.domain

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

/**
 * 优惠券
 *
 * @author 28382
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Coupon {
    
    
    /**
     * 优惠金额
     */
    private BigDecimal amount;

}
import com.mxf.code.product_coupon.enums.ProductTypeEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

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

/**
 * @author mxf
 * @version 1.0
 * @description: 满减
 * @date 2023/6/9
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class FullReduction {
    
    

    private String productType;

    private List<FullReductionItem> fullReductionItemList;

    public static final Map<String, List<FullReductionItem>> FULL_REDUCTION_BY_PRODUCT_TYPE_MAP;

    // 默认数据
    static {
    
    
        FULL_REDUCTION_BY_PRODUCT_TYPE_MAP = new HashMap<>();
        List<FullReductionItem> fullReductionItemList = new ArrayList<>();
        fullReductionItemList.add(new FullReductionItem(new BigDecimal("300"), new BigDecimal("30")));
        fullReductionItemList.add(new FullReductionItem(new BigDecimal("200"), new BigDecimal("10")));
        FULL_REDUCTION_BY_PRODUCT_TYPE_MAP.put(ProductTypeEnum.PRODUCT_TYPE1.getType(), fullReductionItemList);
        List<FullReductionItem> fullReductionItemList2 = new ArrayList<>();
        fullReductionItemList2.add(new FullReductionItem(new BigDecimal("100"), new BigDecimal("5")));
        FULL_REDUCTION_BY_PRODUCT_TYPE_MAP.put(ProductTypeEnum.PRODUCT_TYPE2.getType(), fullReductionItemList2);
    }
}
import lombok.AllArgsConstructor;
import lombok.Data;

import java.math.BigDecimal;

/**
 * @author mxf
 * @version 1.0
 * @description: 满减信息
 * @date 2023/6/9
 */
@Data
@AllArgsConstructor
public class FullReductionItem {
    
    
    /**
     * 满减门槛
     */
    private BigDecimal threshold;
    /**
     * 满减金额
     */
    private BigDecimal reduction;
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 会员
 *
 * @author 28382
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Member {
    
    
    /**
     * 会员等级
     */
    private int level;
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

/**
 * 商品
 *
 * @author 28382
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    
    
    /**
     * 商品名称
     */
    private String name;
    /**
     * 价格
     */
    private BigDecimal price;
    /**
     * 会员商品,可参与会员打折
     */
    private boolean isMemberProduct;
    /**
     * 类型
     */
    private String type;
}

3.2.enums

import lombok.Getter;

import java.math.BigDecimal;
import java.util.stream.Stream;

/**
 * @author mxf
 * @version 1.0
 * @description: 会员等级枚举
 * @date 2023/6/9
 */
@Getter
public enum MemberDiscountEnum {
    
    
    /**
     * 非会员
     */
    NON_MEMBER(Integer.MIN_VALUE, new BigDecimal("1")),
    /**
     * 一级会员
     */
    DISCOUNT_RATE_LEVEL1(1,new BigDecimal("0.9")),
    /**
     * 二级会员
     */
    DISCOUNT_RATE_LEVEL2(2,new BigDecimal("0.85")),
    /**
     * 三级会员
     */
    DISCOUNT_RATE_LEVEL3(3,new BigDecimal("0.8"));
    private final Integer level;
    private final BigDecimal discount;

    MemberDiscountEnum(Integer level, BigDecimal discount) {
    
    
        this.level = level;
        this.discount = discount;
    }

    public static BigDecimal getDiscountByLevel(Integer level) {
    
    
        return Stream.of(MemberDiscountEnum.values())
                .filter(e -> e.getLevel().equals(level))
                .map(MemberDiscountEnum::getDiscount)
                .findFirst().orElse(new BigDecimal("1"));
    }
}
import lombok.Getter;
import lombok.NoArgsConstructor;

/**
 * @author mxf
 * @version 1.0
 * @description: 商品类型枚举
 * @date 2023/6/9
 */
@Getter
@NoArgsConstructor
public enum ProductTypeEnum {
    
    
    /**
     * 商品类型1
     */
    PRODUCT_TYPE1("type1", "类型1"),
    /**
     * 商品类型2
     */
    PRODUCT_TYPE2("type2", "类型2"),
    /**
     * 商品类型3
     */
    PRODUCT_TYPE3("type3", "类型3");
    private String type;
    private String desc;

    ProductTypeEnum(String type, String desc) {
    
    
        this.type = type;
        this.desc = desc;
    }
}

3.3.strategy

import com.mxf.code.product_coupon.domain.Member;
import com.mxf.code.product_coupon.domain.Product;

import java.math.BigDecimal;

/**
 * 折扣策略
 */
public interface DiscountStrategy {
    
    

    BigDecimal calculateDiscountPrice(Product product, Member member);
}
import com.mxf.code.product_coupon.domain.Product;

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

/**
 * 满减策略接口
 *
 * @author 28382
 */
public interface FullReductionStrategy {
    
    

    BigDecimal calculateFullReductionPrice(List<Product> productList);
}
import com.mxf.code.product_coupon.domain.Member;
import com.mxf.code.product_coupon.domain.Product;
import com.mxf.code.product_coupon.enums.MemberDiscountEnum;
import com.mxf.code.product_coupon.strategy.DiscountStrategy;

import java.math.BigDecimal;

/**
 * 会员折扣策略
 *
 * @author 28382
 */
public class MemberDiscountStrategy implements DiscountStrategy {
    
    

    @Override
    public BigDecimal calculateDiscountPrice(Product product, Member member) {
    
    
        return product.getPrice().multiply(MemberDiscountEnum.getDiscountByLevel(member.getLevel()));
    }
}
import com.mxf.code.product_coupon.domain.FullReduction;
import com.mxf.code.product_coupon.domain.FullReductionItem;
import com.mxf.code.product_coupon.domain.Product;
import com.mxf.code.product_coupon.strategy.FullReductionStrategy;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 类型满减策略
 *
 * @author 28382
 */
public class FullReductionForTypeStrategy implements FullReductionStrategy {
    
    

    @Override
    public BigDecimal calculateFullReductionPrice(List<Product> productList) {
    
    
        BigDecimal total = BigDecimal.ZERO;

        Map<String, List<Product>> productMap = productList.stream()
                .collect(Collectors.groupingBy(Product::getType));

        for (Map.Entry<String, List<Product>> entry : productMap.entrySet()) {
    
    
            String productType = entry.getKey();
            List<Product> products = entry.getValue();

            BigDecimal totalProductPrice = products.stream()
                    .map(Product::getPrice)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
                    
            if (FullReduction.FULL_REDUCTION_BY_PRODUCT_TYPE_MAP.containsKey(productType)) {
    
    
                List<FullReductionItem> fullReductionItems = FullReduction.FULL_REDUCTION_BY_PRODUCT_TYPE_MAP.get(productType);
                BigDecimal maxReduction = BigDecimal.ZERO;
                for (FullReductionItem item : fullReductionItems) {
    
    
                    if (totalProductPrice.compareTo(item.getThreshold()) >= 0 && item.getReduction().compareTo(maxReduction) > 0) {
    
    
                        maxReduction = item.getReduction();
                    }
                }
                total = total.add(totalProductPrice.subtract(maxReduction));
            } else {
    
    
                total = total.add(totalProductPrice);
            }
        }
        return total;
    }
}
import com.mxf.code.product_coupon.domain.Coupon;
import com.mxf.code.product_coupon.domain.Member;
import com.mxf.code.product_coupon.domain.Product;

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

/**
 * AbstractPriceCalculator 抽象类
 * 提供了基本的价格计算方法,其中包括判断是否是会员、是否有优惠券、计算总价等方法
 */
public abstract class AbstractPriceCalculator {
    
    
    protected DiscountStrategy discountStrategy;
    protected FullReductionStrategy fullReductionStrategy;

    public AbstractPriceCalculator(DiscountStrategy discountStrategy, FullReductionStrategy fullReductionStrategy) {
    
    
        this.discountStrategy = discountStrategy;
        this.fullReductionStrategy = fullReductionStrategy;
    }

    protected BigDecimal getDiscountPrice(Product product, Member member) {
    
    
        return discountStrategy.calculateDiscountPrice(product, member);
    }

    protected BigDecimal getFullReductionPrice(List<Product> productList) {
    
    
        return fullReductionStrategy.calculateFullReductionPrice(productList);
    }

    protected boolean isMember(Member member) {
    
    
        return member != null && member.getLevel() > 0;
    }

    protected boolean hasCoupon(Coupon coupon) {
    
    
        return coupon != null && coupon.getAmount().compareTo(BigDecimal.ZERO) > 0;
    }

    public BigDecimal calculateTotalPrice(List<Product> productList, Member member, Coupon coupon) {
    
    
        BigDecimal totalPrice;
        for (Product product : productList) {
    
    
            if (isMember(member) && product.isMemberProduct()) {
    
    
                // 会员价
                BigDecimal discountPrice = getDiscountPrice(product, member);
                product.setPrice(discountPrice);
            }
        }
        // 满减
        totalPrice = getFullReductionPrice(productList);
        // 优惠券
        if (hasCoupon(coupon)) {
    
    
            totalPrice = totalPrice.subtract(coupon.getAmount());
        }
        return totalPrice;
    }
}

/**
 * 价格计算器
 * 在需要使用价格计算器的地方注入 PriceCalculator 实例,并调用其方法计算价格
 * @author 28382
 */
public class PriceCalculator extends AbstractPriceCalculator {
    
    

    public PriceCalculator(DiscountStrategy discountStrategy, FullReductionStrategy fullReductionStrategy) {
    
    
        super(discountStrategy, fullReductionStrategy);
    }
}

3.4.service

在需要使用价格计算器的地方注入 PriceCalculator 实例,并调用其方法计算价格。

import com.mxf.code.product_coupon.domain.Coupon;
import com.mxf.code.product_coupon.domain.Member;
import com.mxf.code.product_coupon.domain.Product;

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

/**
 * @author mxf
 * @version 1.0
 * @description: 商品业务类
 * @date 2023/6/12
 */
public interface ProductService {
    
    
    BigDecimal calculatePrice(List<Product> productList, Member member, Coupon coupon);
}
import com.mxf.code.product_coupon.domain.Coupon;
import com.mxf.code.product_coupon.domain.Member;
import com.mxf.code.product_coupon.domain.Product;
import com.mxf.code.product_coupon.service.ProductService;
import com.mxf.code.product_coupon.strategy.PriceCalculator;

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

/**
 * 商品业务实现类
 *
 * @author 28382
 */
public class ProductServiceImpl implements ProductService {
    
    
    private final PriceCalculator priceCalculator;

    public ProductServiceImpl(PriceCalculator priceCalculator) {
    
    
        this.priceCalculator = priceCalculator;
    }

    @Override
    public BigDecimal calculatePrice(List<Product> productList, Member member, Coupon coupon) {
    
    
        return priceCalculator.calculateTotalPrice(productList, member, coupon);
    }
}

3.5.config

import com.mxf.code.product_coupon.domain.Coupon;
import com.mxf.code.product_coupon.domain.Member;
import com.mxf.code.product_coupon.domain.Product;
import com.mxf.code.product_coupon.service.ProductService;
import com.mxf.code.product_coupon.strategy.PriceCalculator;

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

/**
 * 商品业务实现类
 *
 * @author 28382
 */
public class ProductServiceImpl implements ProductService {
    
    
    private final PriceCalculator priceCalculator;

    public ProductServiceImpl(PriceCalculator priceCalculator) {
    
    
        this.priceCalculator = priceCalculator;
    }

    @Override
    public BigDecimal calculatePrice(List<Product> productList, Member member, Coupon coupon) {
    
    
        return priceCalculator.calculateTotalPrice(productList, member, coupon);
    }
}

四、单元测试

import com.mxf.code.product_coupon.config.AppConfig;
import com.mxf.code.product_coupon.domain.Coupon;
import com.mxf.code.product_coupon.domain.Member;
import com.mxf.code.product_coupon.domain.Product;
import com.mxf.code.product_coupon.service.ProductService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

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

import static org.junit.Assert.assertEquals;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {
    
    AppConfig.class})
public class PriceCalculatorTests {
    
    
    @Autowired
    private ProductService productService;

    @Test
    public void testCalculateTotalPrice() {
    
    
        List<Product> productList = new ArrayList<>();
        productList.add(new Product("product1", new BigDecimal("100"), false, "type1"));
        productList.add(new Product("product2", new BigDecimal("150"), true, "type1"));
        productList.add(new Product("product3", new BigDecimal("50"), true, "type1"));

        Member member = new Member();
        member.setLevel(2);

        Coupon coupon = new Coupon();
        coupon.setAmount(new BigDecimal("20"));

        BigDecimal totalPrice = productService.calculatePrice(productList, member, coupon);
        assertEquals(new BigDecimal("240.00"), totalPrice);
    }
}

五、模式应用

  1. 策略模式
    DiscountStrategy 和 FullReductionStrategy 接口以及它们的实现类 MemberDiscountStrategy 和 FullReductionForTypeStrategy 是策略模式的经典应用。通过定义不同的策略接口和具体实现类,可以方便地对不同的业务逻辑进行封装,并且可以在运行时动态地替换不同的策略,从而达到更灵活、更易于扩展的效果。

    在 PriceCalculator 类的构造方法中,我们将不同的策略实例注入进来,并保存在该对象中。在计算价格的时候,我们可以根据需要选择不同的策略进行计算。

  2. 工厂方法模式
    在 AppConfig 类中,我们使用了工厂方法模式来创建不同的 Bean 对象,并将它们注册到 IoC 容器中。例如 memberDiscountStrategy() 方法和 fullReductionForTypeStrategy() 方法分别创建了 MemberDiscountStrategy 和 FullReductionForTypeStrategy 的实例,并将它们注入到 PriceCalculator 的构造方法中。

  3. 单例模式
    在 Spring Boot 中,默认情况下所有的 Bean 都是单例的。因此,我们也可以将 PriceCalculator 设计为单例模式,保证该对象在整个应用程序中只创建一次。在 AppConfig 中,我们使用 @Bean 注解来创建 PriceCalculator 实例,并指定其作用域为 singleton。


六、问题及优化思路

6.1.问题

  • 在 PriceCalculator 中,我们硬编码了两种策略的实现类。如果需要新增一种策略或者更换现有的策略,就需要修改该类的代码。这违反了开闭原则,不利于代码的维护和扩展。
  • 在策略模式中,每个具体策略实现类都需要单独创建一个对象。这可能会导致内存的浪费,尤其是在系统中存在大量不同的策略实现类时。

6.2.优化

  • 使用工厂方法模式或者依赖注入来动态地创建策略实例,从而提高代码的扩展性。例如,可以将不同的策略实现类注册到 IoC 容器中,然后在 PriceCalculator 中根据需要动态获取对应的实例。
  • 使用享元模式来共享策略对象。在该模式中,相同的对象只需要创建一次,并在需要的时候进行共享和重用。可以将策略对象保存在一个 HashMap 中,并根据需要从中获取。这样可以节约内存,同时也能提高对象的复用性和性能。

总结

这次购买商品融入了一些常用的设计模式,可扩展的同时也存在一些不足,对此也提出了一些缺点和优化建议,具体可根据策略模式那篇博文进行适当改进

猜你喜欢

转载自blog.csdn.net/weixin_44063083/article/details/131164015
今日推荐