设计模式与重构:用策略模式替换if-else、用状态模式替换switch、用观察者解耦
说实话,我见过太多代码被if-else和switch塞得满满当当。有一次接手一个支付系统,一个方法里嵌套了七八层if-else,看得我头皮发麻。重构这种事,说白了就是给代码「减肥」——把臃肿的条件判断拆成清晰的设计模式。
今天咱们就聊聊三个最常用的重构套路:策略模式收拾if-else,状态模式收拾switch,观察者模式收拾紧耦合。这三个模式用好了,代码质量能上一个台阶。
一、策略模式:干掉if-else的利器
先看一个典型场景。假设你要写一个订单折扣计算器,不同会员等级折扣不同:
// 重构前:满屏if-else
public double calculateDiscount(String level, double amount) {
if ("VIP".equals(level)) {
return amount * 0.8;
} else if ("GOLD".equals(level)) {
return amount * 0.85;
} else if ("SILVER".equals(level)) {
return amount * 0.9;
} else {
return amount * 0.95;
}
}
这种代码,加一个新等级就要改方法。我在项目中遇到过,上线前产品经理突然说要加「钻石会员」,改完if-else还要重新测试整个流程,太折腾了。
用策略模式重构后:
// 策略接口
public interface DiscountStrategy {
double calculate(double amount);
}
// 具体策略
public class VipDiscount implements DiscountStrategy {
public double calculate(double amount) {
return amount * 0.8;
}
}
public class GoldDiscount implements DiscountStrategy {
public double calculate(double amount) {
return amount * 0.85;
}
}
// 上下文
public class OrderService {
private Map<String, DiscountStrategy> strategies = new HashMap<>();
public OrderService() {
strategies.put("VIP", new VipDiscount());
strategies.put("GOLD", new GoldDiscount());
}
public double calculateDiscount(String level, double amount) {
DiscountStrategy strategy = strategies.get(level);
if (strategy == null) {
throw new IllegalArgumentException("未知会员等级");
}
return strategy.calculate(amount);
}
}
核心变化:把条件判断变成了「查表+多态」。加新等级只需要新增一个策略类,注册到Map里就行,不用改原有代码。
我的习惯:策略模式特别适合「算法族」场景。比如支付方式(微信、支付宝、银行卡)、排序算法、文件导出格式等。只要看到if-else里每个分支做的事情「长得像但做法不同」,就可以考虑策略模式。
二、状态模式:switch的终结者
switch的问题和if-else类似,但状态模式解决的是另一种场景——对象的行为依赖于其内部状态,而且状态会切换。
举个例子,一个订单的状态流转:
// 重构前:switch地狱
public void processOrder(String state) {
switch (state) {
case "NEW":
// 检查库存、锁定库存
break;
case "PAID":
// 发货、更新物流
break;
case "SHIPPED":
// 确认收货
break;
case "COMPLETED":
// 完成评价
break;
default:
throw new IllegalStateException("未知状态");
}
}
我曾经维护过一个工作流引擎,状态有十几种,switch写了上百行。每次加状态都要小心翼翼,生怕漏掉某个case。
用状态模式重构:
// 状态接口
public interface OrderState {
void handle(OrderContext context);
}
// 具体状态
public class NewOrderState implements OrderState {
public void handle(OrderContext context) {
// 检查库存、锁定库存
System.out.println("处理新订单");
context.setState(new PaidOrderState()); // 状态转移
}
}
public class PaidOrderState implements OrderState {
public void handle(OrderContext context) {
// 发货、更新物流
System.out.println("处理已支付订单");
context.setState(new ShippedOrderState());
}
}
// 上下文
public class OrderContext {
private OrderState state;
public OrderContext() {
this.state = new NewOrderState();
}
public void setState(OrderState state) {
this.state = state;
}
public void process() {
state.handle(this);
}
}
避坑指南:我曾经在状态模式里犯过一个错——把状态转移逻辑写在了上下文里。结果上下文越来越胖,又回到了switch的老路。记住:状态转移应该由状态对象自己决定,上下文只负责「当前是什么状态」。
你想想看,状态模式和策略模式的区别在哪?策略模式是「算法替换」,状态模式是「状态流转」。策略模式不改变对象的行为方式,只是换一种算法;状态模式会改变对象的行为,而且状态之间有关联。
三、观察者模式:解耦的万能胶
观察者模式解决的是「一对多」的依赖关系。当一个对象变化时,所有依赖它的对象都能收到通知。
典型的场景是事件驱动系统。比如用户下单后,需要发短信、发邮件、更新库存、记录日志:
// 重构前:硬编码调用
public void placeOrder(Order order) {
// 保存订单
saveOrder(order);
// 发短信
smsService.send(order);
// 发邮件
emailService.send(order);
// 更新库存
inventoryService.update(order);
// 记录日志
logService.record(order);
}
这种代码,加一个「推送微信通知」就要改placeOrder方法。而且如果某个服务挂了,整个下单流程都会受影响。
用观察者模式重构:
// 观察者接口
public interface OrderObserver {
void onOrderPlaced(Order order);
}
// 具体观察者
public class SmsObserver implements OrderObserver {
public void onOrderPlaced(Order order) {
System.out.println("发送短信通知");
}
}
public class EmailObserver implements OrderObserver {
public void onOrderPlaced(Order order) {
System.out.println("发送邮件通知");
}
}
// 被观察者
public class OrderSubject {
private List<OrderObserver> observers = new ArrayList<>();
public void attach(OrderObserver observer) {
observers.add(observer);
}
public void detach(OrderObserver observer) {
observers.remove(observer);
}
public void notifyObservers(Order order) {
for (OrderObserver observer : observers) {
observer.onOrderPlaced(order);
}
}
public void placeOrder(Order order) {
saveOrder(order);
notifyObservers(order); // 通知所有观察者
}
}
关键点:下单逻辑只关心「保存订单」和「通知观察者」。至于观察者是谁、做什么,下单逻辑完全不知道。这就是解耦。
我在项目中用过观察者模式做「插件系统」。核心流程只发布事件,各种插件通过观察者模式订阅事件。加一个新功能,只需要写一个观察者类,注册进去就行,核心代码一行都不用改。
四、三种模式的对比与选择
| 模式 | 解决什么问题 | 典型场景 | 重构对象 |
|---|---|---|---|
| 策略模式 | 算法族替换 | 折扣计算、支付方式、排序 | if-else |
| 状态模式 | 状态流转 | 订单状态、工作流、游戏角色 | switch |
| 观察者模式 | 一对多通知 | 事件驱动、消息推送、日志 | 硬编码调用 |
说白了,这三个模式都是「把变化封装起来」。策略模式封装算法变化,状态模式封装状态变化,观察者模式封装通知变化。你想想看,代码里最容易变的是什么?就是这些条件判断和调用关系。
五、实战中的避坑指南
最后分享几个我踩过的坑:
- 不要过度设计:如果if-else只有两三个分支,而且不会增加,就别用策略模式。我见过有人为了一个「性别判断」也搞策略模式,纯属浪费。
- 状态模式要注意循环:状态转移如果设计不好,可能形成死循环。比如A状态转到B,B状态又转回A。我建议用状态图先画清楚流转路径。
- 观察者模式要防内存泄漏:观察者注册后忘记注销,会导致被观察者持有观察者的引用,无法被GC回收。尤其是Android开发中,Activity销毁时一定要注销观察者。
- 组合使用效果更好:比如策略模式+工厂模式,状态模式+单例模式,观察者模式+事件总线。这些组合我在项目中都用过,效果很好。
我的建议:重构不是一蹴而就的。你可以先写if-else,等代码稳定了,发现确实需要扩展了,再重构。别一开始就想着「我要用设计模式」,那样容易过度设计。
嗯,这三种模式是重构中最常用的。记住一个原则:代码里出现重复的条件判断,就是重构的信号。下次看到if-else、switch、硬编码调用,不妨想想能不能用设计模式来优化。
这三种模式,说白了就是「把变化关进笼子里」。策略模式关算法变化,状态模式关状态变化,观察者模式关通知变化。代码里最怕的就是「牵一发而动全身」,用设计模式把变化隔离好,改起来就轻松多了。