SpringBoot版本: 3.3.0
@Slf4j @Component public class UserCache extends AbstractCache<Long, User> { private final Map<Long, User> userMap = new HashMap<>(); /** * 初始化缓存 */ @Override public void init() { this.userMap.put(1L, User.builder().id(1L).name("Lucy").gender("女").age(18).type(0).build()); this.userMap.put(2L, User.builder().id(2L).name("Jack").gender("男").age(20).type(1).build()); this.userMap.put(3L, User.builder().id(3L).name("Mick").gender("男").age(22).type(2).build()); this.userMap.put(4L, User.builder().id(4L).name("Andy").gender("女").age(23).type(3).build()); this.userMap.put(5L, User.builder().id(5L).name("Pelin").gender("女").age(23).type(4).build()); log.info("user cache init success: {}", JSON.toJSONString(this.userMap)); } /** * 获取缓存 * * @param key 键 * @return 值 */ @Override public User get(Long key) { return this.userMap.get(key); } /** * 清空缓存 */ @Override public void clear() { this.userMap.clear(); } }商品缓存
@Slf4j @Component public class MerchandiseCache extends AbstractCache<Long, Merchandise> { private final Map<Long, Merchandise> merchandiseMap = new HashMap<>(); // 堆代码 duidaima.com @Override public void init() { this.merchandiseMap.put(1L, Merchandise.builder().id(1L).name("小米14").price(BigDecimal.valueOf(3999.00)).build()); this.merchandiseMap.put(2L, Merchandise.builder().id(2L).name("小米路由器").price(BigDecimal.valueOf(399.00)).build()); this.merchandiseMap.put(3L, Merchandise.builder().id(3L).name("小米扫地机器人").price(BigDecimal.valueOf(2299.00)).build()); this.merchandiseMap.put(4L, Merchandise.builder().id(4L).name("小米电视S75英寸4K").price(BigDecimal.valueOf(3899.00)).build()); this.merchandiseMap.put(5L, Merchandise.builder().id(5L).name("小米SoundPro").price(BigDecimal.valueOf(899)).build()); log.info("merchandise cache init success: {}", JSON.toJSONString(this.merchandiseMap)); } @Override public Merchandise get(Long key) { return this.merchandiseMap.get(key); } @Override public void clear() { this.merchandiseMap.clear(); } }折扣枚举
@Getter public enum DiscountEnum { NORMAL(0, "普通用户", BigDecimal.valueOf(1.00)), VIP(1, "普通VIP", BigDecimal.valueOf(0.95)), PVIP(2, "高级VIP", BigDecimal.valueOf(0.90)), EVIP(4, "至尊VIP", BigDecimal.valueOf(0.85)), SVIP(3, "超级VIP", BigDecimal.valueOf(0.80)); /** * 用户类型code */ private final int code; /** * 用户类型文字 */ private final String type; /** * 折扣 */ private final BigDecimal discount; DiscountEnum(int code, String type, BigDecimal discount) { this.code = code; this.type = type; this.discount = discount; } /** * 根据code获取枚举 * * @param code code * @return DiscountEnum */ public static DiscountEnum valueOf(int code) { for (DiscountEnum value : DiscountEnum.values()) { if (value.getCode() == code) { return value; } } return null; } }大量 if else 的弊端
@Slf4j @Service("shoppingService1") public class ShoppingService1Impl implements ShoppingService { @Resource private UserCache userCache; @Resource private MerchandiseCache merchandiseCache; /** * 购买商品 * * @param userId 用户ID * @param merchandiseId 商品ID * @return 购买结果 */ @Override public Map<String, Object> buy(Long userId, Long merchandiseId) { User user = userCache.get(userId); Merchandise merchandise = merchandiseCache.get(merchandiseId); log.info("current user: {}", JSON.toJSONString(user)); log.info("current merchandise: {}", JSON.toJSONString(merchandise)); DecimalFormat df = new DecimalFormat("0.00"); DiscountEnum discountEnum = DiscountEnum.valueOf(user.getType()); log.info("user type: {}, 享受折扣: {}", discountEnum.getType(), df.format(discountEnum.getDiscount())); // 普通用户 if (user.getType() == 0) { return Map.of("用户姓名", user.getName(), "商品名称", merchandise.getName(), "商品价格", merchandise.getPrice(), "折后价格", merchandise.getPrice().multiply(discountEnum.getDiscount())); } else // 普通VIP if (user.getType() == 1) { return Map.of("用户姓名", user.getName(), "用户性别", user.getGender(), "用户等级", discountEnum.getType(), "商品名称", merchandise.getName(), "商品价格", merchandise.getPrice(), "应享折扣", discountEnum.getDiscount(), "折后价格", merchandise.getPrice().multiply(discountEnum.getDiscount())); } else // 高级VIP if (user.getType() == 2) { return Map.of( "用户姓名", user.getName(), "用户年龄", user.getAge(), "用户等级", discountEnum.getType(), "商品名称", merchandise.getName(), "商品价格", merchandise.getPrice(), "应享折扣", discountEnum.getDiscount(), "折后价格", merchandise.getPrice().multiply(discountEnum.getDiscount())); } else // 至尊VIP if (user.getType() == 3) { return Map.of("用户姓名", user.getName(), "用户性别", user.getGender(), "用户年龄", user.getAge(), "用户等级", discountEnum.getType(), "商品名称", merchandise.getName(), "商品价格", merchandise.getPrice(), "应享折扣", discountEnum.getDiscount(), "折后价格", merchandise.getPrice().multiply(discountEnum.getDiscount())); } else // 超级VIP if (user.getType() == 4) { return Map.of("用户ID", user.getId(), "用户姓名", user.getName(), "用户性别", user.getGender(), "用户年龄", user.getAge(), "用户等级", discountEnum.getType(), "商品名称", merchandise.getName(), "商品价格", merchandise.getPrice(), "应享折扣", discountEnum.getDiscount(), "折后价格", merchandise.getPrice().multiply(discountEnum.getDiscount())); } return Map.of(); } }
其实问题很容易看出来,就是这样的代码太臃肿了,也违反了开闭原则(对扩展开放,对修改关闭),将来又加入新的用户类型,还需要在这基础上继续添加逻辑代码,实在是太不优雅了。为了解决这个问题呢,先是引入了策略模式,其实单纯的策略模式还是不能够解决全部问题,大家继续往下看。
public interface ShoppingStrategyAware { /** * 购买 * * @param user 用户 * @param merchandise 商品 * @return 结果 */ Map<String, Object> buy(User user, Merchandise merchandise); }再将不同的用户类型当做不同的策略分别创建一个类来继承这个策略接口,案例中一共有五个用户类型,全部代码放上来就太多啦(普通用户、普通VIP、高级VIP、至尊VIP、超级VIP),这里放两个进行举例,完整的请查看下边的 github 源码链接。
@Slf4j @Component public class NormalStrategy implements ShoppingStrategyAware { @Override public Map<String, Object> buy(User user, Merchandise merchandise) { log.info("current strategy: {}", getStrategy().getType()); DecimalFormat df = new DecimalFormat("0.00"); Map<String, Object> result = new HashMap<>(); result.put("用户姓名", user.getName()); result.put("商品名称", merchandise.getName()); result.put("商品原价", df.format(merchandise.getPrice())); result.put("折后价格", df.format(merchandise.getPrice().multiply(getStrategy().getDiscount()))); return result; } }普通VIP
@Slf4j @Component public class VIPStrategy implements ShoppingStrategyAware { @Override public Map<String, Object> buy(User user, Merchandise merchandise) { log.info("current strategy: {}", getStrategy().getType()); DecimalFormat df = new DecimalFormat("0.00"); Map<String, Object> result = new HashMap<>(); result.put("用户姓名", user.getName()); result.put("用户性别", user.getGender()); result.put("用户等级", getStrategy().getType()); result.put("商品名称", merchandise.getName()); result.put("商品原价", df.format(merchandise.getPrice())); result.put("应享折扣", df.format(getStrategy().getDiscount())); result.put("折后价格", df.format(merchandise.getPrice().multiply(getStrategy().getDiscount()))); return result; } }将上边大量 if else 进行改造,将不同的策略在业务中进行注入:
@Slf4j @Service("shoppingService2") public class ShoppingService2Impl implements ShoppingService { @Resource private UserCache userCache; @Resource private MerchandiseCache merchandiseCache; @Resource(name = "normalStrategy") private ShoppingStrategyAware normalStrategy; @Resource(name = "VIPStrategy") private ShoppingStrategyAware vipStrategy; @Resource(name = "PVIPStrategy") private ShoppingStrategyAware pvipStrategy; @Resource(name = "EVIPStrategy") private ShoppingStrategyAware evipStrategy; @Resource(name = "SVIPStrategy") private ShoppingStrategyAware svipStrategy; /** * 购买商品 * * @param userId 用户ID * @param merchandiseId 商品ID * @return 购买结果 */ @Override public Map<String, Object> buy(Long userId, Long merchandiseId) { User user = userCache.get(userId); Merchandise merchandise = merchandiseCache.get(merchandiseId); log.info("current user: {}", JSON.toJSONString(user)); log.info("current merchandise: {}", JSON.toJSONString(merchandise)); // 普通用户 if (user.getType() == 0) { return normalStrategy.buy(user, merchandise); } else // 普通VIP if (user.getType() == 1) { return vipStrategy.buy(user, merchandise); } else // 高级VIP if (user.getType() == 2) { return pvipStrategy.buy(user, merchandise); } else // 至尊VIP if (user.getType() == 3) { return evipStrategy.buy(user, merchandise); } else // 超级VIP if (user.getType() == 4) { return svipStrategy.buy(user, merchandise); } return Map.of(); } }我相信聪明的小伙伴们,一眼就能发现弊端,虽然业务逻辑进行了封装,但是,这也注入太多策略了,而且,还是不能避免使用大量的 if else,那么这样还需要如何进行优化呢?其实,再加一个工厂模式就能完美解决。
/** * 返回应享折扣枚举 * * @return 折扣 */ DiscountEnum getStrategy();再将具体的策略进行方法重写,将策略的枚举进行返回即可:
@Override public DiscountEnum getStrategy() { log.info("load strategy: {}", JSON.toJSONString(DiscountEnum.NORMAL)); return DiscountEnum.NORMAL; }普通VIP
@Override public DiscountEnum getStrategy() { log.info("load strategy: {}", JSON.toJSONString(DiscountEnum.VIP)); return DiscountEnum.VIP; }然后我们定义一个工厂类:
@Slf4j @Component public class ShoppingFactory { @Resource private List<ShoppingStrategyAware> shoppingStrategies; private final Map<DiscountEnum, ShoppingStrategyAware> shoppingMap = new HashMap<>(); /** * 根据code获取折扣策略 * * @param code code * @return DiscountEnum */ public ShoppingStrategyAware getStrategy(int code) { DiscountEnum discountEnum = DiscountEnum.valueOf(code); return shoppingMap.get(discountEnum); } /** * 注册策略枚举 */ @PostConstruct public void register() { shoppingStrategies.forEach(shoppingStrategy -> { DiscountEnum discount = shoppingStrategy.getStrategy(); shoppingMap.put(discount, shoppingStrategy); }); } }在上边的工厂类里,我注入了一个List类型的策略接口和一个Map缓存,用于存放折扣枚举对应的策略,然后在下边register方法上加了一个@PostConstruct注解,目的是将所有的策略在项目启动时就加载好。然后再提供一个根据用户类型获取策略的方法getStrategy,这样之后,我们就又可以对代码进行改造了,在业务层,我们只需要注入一个策略工厂就好了:
@Service("shoppingService3") public class ShoppingService3Impl implements ShoppingService { @Resource private ShoppingFactory shoppingFactory; @Resource private UserCache userCache; @Resource private MerchandiseCache merchandiseCache; /** * 购买 * * @param userId 用户ID * @param merchandiseId 商品ID * @return 购买结果 */ @Override public Map<String, Object> buy(Long userId, Long merchandiseId) { User user = userCache.get(userId); Merchandise merchandise = merchandiseCache.get(merchandiseId); log.info("current user: {}", JSON.toJSONString(user)); log.info("current merchandise: {}", JSON.toJSONString(merchandise)); ShoppingStrategyAware strategy = shoppingFactory.getStrategy(user.getType()); return strategy.buy(user, merchandise); } }可以看到,业务逻辑那里,通过调用工厂的getStrategy方法传入用户类型来获取具体策略,然后调用策略的buy方法就好了,这样的好处就是,将来不管怎么扩展,我们都不需要更改这里的代码,只需要,继续实现策略接口,然后实现具体的业务逻辑就好啦,是不是很优雅呢~