文章目录
- 前言
- 一、代码结构
- 二、UML图
- 三、代码实现
- 3.1.domain
- 3.2.enums
- 3.3.strategy
- 3.4.service
- 3.5.config
- 四、单元测试
- 五、模式应用
- 六、问题及优化思路
- 6.1.问题
- 6.2.优化
- 总结
前言
使用策略模式、工厂方法模式、单例模式实现一些购买策略,需求:商城商品根据用户是否是会员,如果是会员则根据会员等级进行商品原价折扣,再根据商品是否参加满减,如果参加满减则对同一类型满减的商品进行合计去掉满减金额,然后再根据用户是否使用优惠券,在进行则扣。
一、代码结构
二、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);
}
}
五、模式应用
-
策略模式
DiscountStrategy 和 FullReductionStrategy 接口以及它们的实现类 MemberDiscountStrategy 和 FullReductionForTypeStrategy 是策略模式的经典应用。通过定义不同的策略接口和具体实现类,可以方便地对不同的业务逻辑进行封装,并且可以在运行时动态地替换不同的策略,从而达到更灵活、更易于扩展的效果。在 PriceCalculator 类的构造方法中,我们将不同的策略实例注入进来,并保存在该对象中。在计算价格的时候,我们可以根据需要选择不同的策略进行计算。
-
工厂方法模式
在 AppConfig 类中,我们使用了工厂方法模式来创建不同的 Bean 对象,并将它们注册到 IoC 容器中。例如 memberDiscountStrategy() 方法和 fullReductionForTypeStrategy() 方法分别创建了 MemberDiscountStrategy 和 FullReductionForTypeStrategy 的实例,并将它们注入到 PriceCalculator 的构造方法中。 -
单例模式
在 Spring Boot 中,默认情况下所有的 Bean 都是单例的。因此,我们也可以将 PriceCalculator 设计为单例模式,保证该对象在整个应用程序中只创建一次。在 AppConfig 中,我们使用 @Bean 注解来创建 PriceCalculator 实例,并指定其作用域为 singleton。
六、问题及优化思路
6.1.问题
- 在 PriceCalculator 中,我们硬编码了两种策略的实现类。如果需要新增一种策略或者更换现有的策略,就需要修改该类的代码。这违反了开闭原则,不利于代码的维护和扩展。
- 在策略模式中,每个具体策略实现类都需要单独创建一个对象。这可能会导致内存的浪费,尤其是在系统中存在大量不同的策略实现类时。
6.2.优化
- 使用工厂方法模式或者依赖注入来动态地创建策略实例,从而提高代码的扩展性。例如,可以将不同的策略实现类注册到 IoC 容器中,然后在 PriceCalculator 中根据需要动态获取对应的实例。
- 使用享元模式来共享策略对象。在该模式中,相同的对象只需要创建一次,并在需要的时候进行共享和重用。可以将策略对象保存在一个 HashMap 中,并根据需要从中获取。这样可以节约内存,同时也能提高对象的复用性和性能。
总结
这次购买商品融入了一些常用的设计模式,可扩展的同时也存在一些不足,对此也提出了一些缺点和优化建议,具体可根据策略模式那篇博文进行适当改进