男儿当入樽正版
1.51 GB · 2025-12-15
各种社区里,你一定见过无数篇标题类似"彻底消灭 if-else"、"告别 if-else 地狱"的文章。这些文章通常会展示一段充满 if-else 的"坏代码",然后用策略模式、工厂模式或者其他设计模式将其"重构"成看起来更优雅的代码。这确实也没啥问题。但是我觉得出发点不太对。不是因为if-else太多我们要重构,而是因为分支耦合在一起,修改一个分支容易影响其他业务,从而产生风险,所以我们需要重构。
if-else 不是问题,它只是表象。我们重构代码的目的不是为了消灭 if-else,而是为了控制风险。
比如我们项目中的会员价格服务
核心业务逻辑如下:
@Override
public void memberPriceAddOrModify(MemberPriceAddOrModifyParam param) {
LogUtil.info(log, "memberPriceAddOrModify >> 商品会员价新增和修改统一服务开始 >> param = {}", param);
// 参数校验
ValidateUtil.validateWithThrow(param);
// 获取goodsIdList
List<String> goodsIdList = param.getGoodsPriceList().stream()
.map(GoodsPriceInfoParam::getGoodsId)
.collect(Collectors.toList());
goodsIdList = goodsIdList.stream().distinct().collect(Collectors.toList());
if (goodsIdList.size() == 0) {
LogUtil.info(log, "memberPriceAddOrModify >> 商品会员价新增和修改统一服务结束 >> param = {}", param);
return;
}
// 判断商品档案/门店商品/网店商品类型
if (param.getBelong().equals(BelongEnum.MERCHANT.getValue())) {
dealUserGoodsPriceExt(goodsIdList, param);
} else if (param.getBelong().equals(BelongEnum.STORE.getValue())) {
dealStoreGoodsPriceExt(goodsIdList, param);
} else if (param.getBelong().equals(BelongEnum.ONLINE_STORE.getValue())) {
dealOnlineGoodsPriceExt(goodsIdList, param);
}
LogUtil.info(log, "memberPriceAddOrModify >> 商品会员价新增和修改统一服务结束 >> param = {}", param);
}
第一眼看上去,这段代码有明显的 if-else 链。按照传统的"消灭 if-else"思路,我们应该考虑引入策略模式,去优雅的实现。
但仔细想想if-else有问题吗? 每个分支也通过函数进行隔离,就是七八个又能怎么样? 也不会有太多的理解成本。那为啥要重构?
可预见的风险点:
让我们思考一个真实场景:
问题来了:如果这三个分支的逻辑都在 PriceServiceImpl 这一个类中,那么:
所以真正的风险不在于有 if-else,而在于:
所以很顺畅的就能想到。我们不是为了消灭 if-else,而是为了让每个分支的修改只影响自己的业务。
真正的"隔离"不是提取一个公共方法,而是将不同业务的代码物理隔离:
// 商户商品价格服务 - 独立的类
@Service
public class MerchantGoodsPriceService {
@Autowired
private UserGoodsPriceExtDAO userGoodsPriceExtDAO;
/**
* 处理商户商品价格
* 这个类只负责商户商品,修改不会影响门店和网店
*/
public void handleMemberPrice(List<String> goodsIdList, MemberPriceAddOrModifyParam param) {
TransactionStatus transaction = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
userGoodsPriceExtDAO.delByGoodsIdListAndPriceType(param.getGsUid(), param.getPriceType(), goodsIdList);
param.getGoodsPriceList().forEach(info -> {
// ... 插入逻辑 ...
});
transactionManager.commit(transaction);
} catch (Exception e) {
LogUtil.error(log, "商户商品价格处理异常", e);
transactionManager.rollback(transaction);
throw e;
}
}
}
// 门店商品价格服务 - 独立的类
@Service
public class StoreGoodsPriceService {
@Autowired
private StoreGoodsPriceExtDAO storeGoodsPriceExtDAO;
/**
* 处理门店商品价格
* 这个类只负责门店商品,可以独立演化
*/
public void handleMemberPrice(List<String> goodsIdList, MemberPriceAddOrModifyParam param) {
// 类似实现,但可以独立修改
}
}
// 网店商品价格服务 - 独立的类
@Service
public class OnlineGoodsPriceService {
@Autowired
private OnlineGoodsPriceExtDAO onlineGoodsPriceExtDAO;
/**
* 处理网店商品价格
* 这个类只负责网店商品,可以有自己的特殊逻辑
*/
public void handleMemberPrice(List<String> goodsIdList, MemberPriceAddOrModifyParam param) {
// 类似实现,但可以独立修改
}
}
好处:
MerchantGoodsPriceService,不会影响门店和网店虽然我们已经将业务隔离到不同的类中,但你会发现三个类中的代码逻辑高度相似。这时候需要思考:这是偶然重复还是本质重复?
判断标准:
假设这是本质重复,我们可以提取一个抽象基类:
// 抽象基类 - 定义通用流程
public abstract class AbstractGoodsPriceService<T> {
protected abstract DAO<T> getDAO();
protected abstract T buildPriceDO(GoodsPriceInfo info, PriceInfo priceInfo, MemberPriceAddOrModifyParam param);
/**
* 通用的价格处理流程
*/
public void handleMemberPrice(List<String> goodsIdList, MemberPriceAddOrModifyParam param) {
TransactionStatus transaction = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// 删除旧数据
getDAO().delByGoodsIdListAndPriceType(param.getGsUid(), param.getPriceType(), goodsIdList);
// 批量插入新数据
List<T> dataList = new ArrayList<>();
param.getGoodsPriceList().forEach(info -> {
if (StrUtil.isNotBlank(info.getGoodsId()) &&
CollectionUtil.isNotEmpty(info.getPriceInfoParamList())) {
info.getPriceInfoParamList().forEach(priceInfo -> {
if (priceInfo.getPrice() != null &&
priceInfo.getPrice().compareTo(BigDecimal.ZERO) > 0) {
dataList.add(buildPriceDO(info, priceInfo, param));
}
});
}
});
if (CollectionUtil.isNotEmpty(dataList)) {
getDAO().batchInsert(dataList);
}
transactionManager.commit(transaction);
} catch (Exception e) {
LogUtil.error(log, "价格处理异常", e);
transactionManager.rollback(transaction);
throw e;
}
}
}
// 商户商品价格服务 - 只需实现差异部分
@Service
public class MerchantGoodsPriceService extends AbstractGoodsPriceService<UserGoodsPriceExtDO> {
@Autowired
private UserGoodsPriceExtDAO userGoodsPriceExtDAO;
@Override
protected DAO<UserGoodsPriceExtDO> getDAO() {
return userGoodsPriceExtDAO;
}
@Override
protected UserGoodsPriceExtDO buildPriceDO(GoodsPriceInfo info, PriceInfo priceInfo, MemberPriceAddOrModifyParam param) {
UserGoodsPriceExtDO userGoodsPriceExtDO = new UserGoodsPriceExtDO();
userGoodsPriceExtDO.setGsUid(param.getGsUid());
userGoodsPriceExtDO.setPriceType(param.getPriceType());
userGoodsPriceExtDO.setPrice(priceInfo.getPrice());
userGoodsPriceExtDO.setUserGoodsId(info.getGoodsId());
userGoodsPriceExtDO.setRightConfigNo(priceInfo.getRightNo());
userGoodsPriceExtDO.setDiscountType(priceInfo.getDiscountType());
return userGoodsPriceExtDO;
}
}
但要注意:
到这一步,其实我们已经完成了最重要的优化:业务影响面隔离。现在才真正需要思考:是否需要消除 if-else?
这个问题的本质是:在可读性、开闭原则和设计模式之间如何折中?
@Service
public class PriceServiceImpl implements PriceService {
@Autowired
private MerchantGoodsPriceService merchantGoodsPriceService;
@Autowired
private StoreGoodsPriceService storeGoodsPriceService;
@Autowired
private OnlineGoodsPriceService onlineGoodsPriceService;
@Override
public void memberPriceAddOrModify(MemberPriceAddOrModifyParam param) {
// ... 参数校验 ...
// 清晰的路由逻辑:一眼就能看出系统支持哪几种商品类型
if (param.getBelong().equals(BelongEnum.MERCHANT.getValue())) {
merchantGoodsPriceService.handleMemberPrice(goodsIdList, param);
} else if (param.getBelong().equals(BelongEnum.STORE.getValue())) {
storeGoodsPriceService.handleMemberPrice(goodsIdList, param);
} else if (param.getBelong().equals(BelongEnum.ONLINE_STORE.getValue())) {
onlineGoodsPriceService.handleMemberPrice(goodsIdList, param);
} else {
throw new IllegalArgumentException("不支持的商品归属类型: " + param.getBelong());
}
}
}
可读性极佳
修改成本可控
符合业务现实
@Service
public class PriceServiceImpl implements PriceService {
private final Map<BelongEnum, PriceHandler> handlerMap;
public PriceServiceImpl(List<PriceHandler> handlers) {
// 通过 Spring 自动注入所有 PriceHandler 实现
this.handlerMap = handlers.stream()
.collect(Collectors.toMap(PriceHandler::supportedBelong, Function.identity()));
}
@Override
public void memberPriceAddOrModify(MemberPriceAddOrModifyParam param) {
// ... 参数校验 ...
PriceHandler handler = handlerMap.get(BelongEnum.valueOf(param.getBelong()));
if (handler == null) {
throw new IllegalArgumentException("不支持的商品归属类型: " + param.getBelong());
}
handler.handleMemberPrice(goodsIdList, param);
}
}
优势分析:
符合开闭原则
PriceHandler 实现类PriceServiceImpl支持扩展
但代价是什么?
可读性变差
PriceHandler 的实现类过度抽象
开闭原则的滥用
在业务开发中,修改原有类是常态,不是什么罪过
新增商品类型时,除了加新类,你还要:
BelongEnum(不可避免)那为什么唯独不能修改 PriceServiceImpl 呢?
在业务开发中,完全遵循开闭原则往往是一种过度设计。开闭原则的价值在于应对频繁变化,而不是僵化地避免修改代码。"
可读性是第一生产力
业务开发避免不了修改原有类
投入产出比
YAGNI 原则(You Aren't Gonna Need It)
回到文章开头的观点:重构代码不是为了消灭 if-else,而是为了控制风险。
一个好的重构流程应该是:
识别风险:每个分支的修改是否会影响其他业务?分支内部是否有复杂的业务逻辑?
隔离业务影响面:将不同的业务逻辑隔离到独立的类中,让每个分支的修改只影响自己
谨慎消除重复:判断代码重复是本质重复还是偶然重复
评估扩展性:未来是否会频繁新增分支?
思考的时候要明白:
隔离比消除更重要
简单比优雅更重要
风险可控比代码优雅更重要
作为普通的程序员,我们的首要任务是让代码的修改影响面可控,而不是写出"看起来很高级"的代码。在实际工作中:
叠甲叠甲: 我不是反对把if-else重构成策略模式,这完全没问题,是个人的选择。我只是if-else分支太多不是我们重构代码的核心理由,通过消解ifelse来控制风险才是原因。