⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7375386125798506522 「寒江雪369」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

Hello,大家好。大家工作中有没有在一个业务使用过大量的if else啊,如果有,你看到这篇文章就能去优化自己的代码了,兄弟们赶紧冲啊!!!

在这篇文章中,我将使用会员制度打折促销为案例,为大家循序渐进的讲述使用工厂+策略模式之优雅。

Java版本: 21

SpringBoot版本: 3.3.0

案例前置

Demo 中没有使用数据库相关内容,而是使用缓存热启动提前在 Map 中加载了些参数,存在用户(id, name, gender, age, type)与商品(id, name, price)实体,之前我写过关于缓存热启动的文章,大家有兴趣可以去看看,这里放个具体的缓存代码,在这个 Demo 中,缓存热启动也做了优化,我会把源码 github 链接放在下边,大家也可以进行参阅:

用户缓存

@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<>();

@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 的弊端

大家看,下边这段根据用户类型(type)进行打折促销的代码:

@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方法就好了,这样的好处就是,将来不管怎么扩展,我们都不需要更改这里的代码,只需要,继续实现策略接口,然后实现具体的业务逻辑就好啦,是不是很优雅呢~

看到这里的小伙伴,为努力的自己点个赞吧!欢迎留言,文章中如有错误,请指出,我会进行修改,感谢大家的阅读,我们下期再见啦~~~

Demo 源码地址

https://github.com/hanjx369/shopping

文章目录
  1. 1. 案例前置
    1. 1.0.0.1. 用户缓存
    2. 1.0.0.2. 商品缓存
    3. 1.0.0.3. 折扣枚举
  • 2. 大量 if else 的弊端
  • 3. 策略模式
    1. 3.1. 普通用户
    2. 3.2. 普通VIP
  • 4. 引入工厂模式
    1. 4.0.0.1. 普通用户
    2. 4.0.0.2. 普通VIP