文章目录
- 前言
- 一、代码结构
- 二、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 {@Overridepublic 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 {@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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 {@Autowiredprivate ProductService productService;@Testpublic 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 中,并根据需要从中获取。这样可以节约内存,同时也能提高对象的复用性和性能。
总结
这次购买商品融入了一些常用的设计模式,可扩展的同时也存在一些不足,对此也提出了一些缺点和优化建议,具体可根据策略模式那篇博文进行适当改进