软件设计原则之开闭原则
开闭原则:让你的代码更优雅地面对变化
在软件开发中,我们经常面对需求的不断变更与功能的持续扩展。如果每次变更都需要改动大量旧代码,不仅效率低,还容易引入新的 bug。这时,设计原则中的“开闭原则”(Open-Closed Principle, OCP)就显得尤为重要。
一、什么是开闭原则
定义:
软件实体(类、模块、方法等)对扩展开放,对修改关闭。
简单来说:
-
对扩展开放(Open for extension):当需求变化时,系统可以通过添加新代码来适应,而不是改动原来的代码。
-
对修改关闭(Closed for modification):一旦系统模块稳定,就不应该轻易修改其内部逻辑,以免破坏已有功能。
这种设计思想能显著提高系统的可维护性、可复用性和可扩展性。
二、为什么要遵循开闭原则?
在实际开发中,项目上线后往往还会持续迭代,需求也会不断变动。如果每次都修改原来的代码:
-
很容易破坏已有功能;
-
引入新的 bug;
-
影响已有用户;
-
难以维护和测试。
开闭原则的核心价值在于——将“变化”隔离,把“稳定”沉淀。也就是说,稳定的代码不动,把所有变化都通过“扩展”来实现。
✦ 如何实现变化的隔离?
我们通过抽象接口 + 多态实现,将可变逻辑(变化点)封装到独立的策略类、扩展类中,而主流程(稳定部分)则依赖这些接口。
这样,主流程永远不需要因为新增的规则而修改自己,只要新增一个扩展类,实现接口,并注册进去即可。
这就像造房子时,提前预留了插座和接口,后期你可以插不同的电器,但不需要敲墙重新布线。
三、一个真实的生产场景
我们以“订单价格计算系统”为例。公司要求对不同的用户类型使用不同的价格策略:
普通用户:原价
VIP 用户:9 折
员工:7 折
初始版本(违背开闭原则):
点击查看代码
public class PriceCalculator {
/**
* 计算价格
* @param basePrice 基础价格
* @param userType 用户类型
* @return 价格
*/
public BigDecimal calculatePrice(BigDecimal basePrice, String userType) {
if ("NORMAL".equals(userType)) {
return basePrice;
} else if ("VIP".equals(userType)) {
return basePrice.multiply(new BigDecimal("0.9"));
} else if ("EMPLOYEE".equals(userType)) {
return basePrice.multiply(new BigDecimal("0.7"));
}
return basePrice;
}
}
这种方式的问题是:
-
每加一个用户类型都要改 PriceCalculator;
-
if-else 很快就变得难以维护;
不符合开闭原则。
四、重构:使用策略模式 + 开闭原则
- 定义用户类型枚举
点击查看代码
@AllArgsConstructor
@Getter
public enum UserTypeEnum {
NORMAL("NORMAL", "普通用户"),
VIP("VIP", "VIP用户"),;
private final String code;
private final String desc;
}
点击查看代码
public interface PriceStrategy {
UserTypeEnum userType();
BigDecimal calculate(BigDecimal basePrice);
}
- 实现不同的策略类:
点击查看代码
@Component
public class NormalUserStrategy implements PriceStrategy {
@Override
public UserTypeEnum userType() {
return UserTypeEnum.NORMAL;
}
@Override
public BigDecimal calculate(BigDecimal basePrice) {
return basePrice;
}
}
@Component
public class VipUserStrategy implements PriceStrategy {
@Override
public UserTypeEnum userType() {
return UserTypeEnum.VIP;
}
@Override
public BigDecimal calculate(BigDecimal basePrice) {
return basePrice.multiply(new BigDecimal("0.9"));
}
}
- 策略上下文(策略选择器):
点击查看代码
/**
* @author yumai
* @version JDK 8
* @date 2025/5/27
* @description 策略上下文
*/
@Component
public class PriceStrategyContext implements InitializingBean {
/**注入策略实现列表*/
@Autowired
private List<PriceStrategy> priceStrategies;
private static Map<UserTypeEnum, PriceStrategy> strategyMap = new HashMap<>();
/**
* 初始化策略实现在map<userTypeEnum, priceStrategy>,防止重复,便于后续根据用户类型获取策略实现
*
* @throws Exception 异常
*/
@Override
public void afterPropertiesSet() throws Exception {
priceStrategies.forEach(strategy -> {
PriceStrategy priceStrategy = strategyMap.get(strategy.userType());
if (Objects.nonNull(priceStrategy)){
throw new RuntimeException("存在相同的策略类型:" + strategy.userType());
}
strategyMap.put(strategy.userType(), strategy);
});
}
/**
* 根据用户类型计算价格 (根据具体的用户类型,选择对应的策略实现,并调用其calculate方法)
* @param userType 类型
* @param basePrice 基础价格
* @return 价格
*/
public BigDecimal calculate(UserTypeEnum userType, BigDecimal basePrice) {
PriceStrategy priceStrategy = strategyMap.get(userType);
if (Objects.isNull(priceStrategy)){
throw new RuntimeException("不存在策略类型:" + userType);
}
return priceStrategy.calculate(basePrice);
}
}
- 客户端使用
点击查看代码
@RestController
@RequestMapping(value = "/price")
public class PriceController {
@Autowired
private PriceStrategyContext context;
@PostMapping
public BigDecimal getPrice(){
return context.calculate(UserTypeEnum.VIP, new BigDecimal("100"));
}
}
五、扩展新功能时的好处
假设新增一个“员工用户”,享受7折,只需新建一个策略类,在枚举中增加员工用户类型:
点击查看代码
@AllArgsConstructor
@Getter
public enum UserTypeEnum {
NORMAL("NORMAL", "普通用户"),
VIP("VIP", "VIP用户"),
EMPLOYEE("EMPLOYEE", "员工");
private final String code;
private final String desc;
}
@Component
public class EmployeeUserStrategy implements PriceStrategy {
@Override
public UserTypeEnum userType() {
return UserTypeEnum.EMPLOYEE;
}
@Override
public BigDecimal calculate(BigDecimal basePrice) {
return basePrice.multiply(new BigDecimal("0.7"));
}
}
针对于业务上用户类型的增加,需要不同的打折逻辑,这些就是变化点,也就是我们需要开放的部分,而策略上下文、策略接口主流程的部分是稳定的。
六、结束语
开闭原则并不追求“永不修改”的代码,而是希望我们将变化封装起来,让系统在面对不断增长的需求时依然保持“稳定”。如果你希望写出易扩展、少出错、能复用的代码,开闭原则一定是你必须掌握和践行的重要准则之一。
记住:不是所有的类都需要一开始就设计得非常通用,但一旦“变化”成为常态,就必须让系统遵循开闭原则。

浙公网安备 33010602011771号