设计模式与重构:用策略模式替换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、硬编码调用,不妨想想能不能用设计模式来优化。

设计模式重构核心逻辑 策略模式 替换 if-else Context 上下文 Strategy 接口 策略A 策略B 策略C 算法族可替换 状态模式 替换 switch Context 上下文 State 接口 状态A 状态B 状态自动流转 观察者模式 解耦调用 Subject 被观察者 Observer 接口 观察者A 观察者B 观察者C 一对多通知 核心思想:封装变化,面向接口编程

这三种模式,说白了就是「把变化关进笼子里」。策略模式关算法变化,状态模式关状态变化,观察者模式关通知变化。代码里最怕的就是「牵一发而动全身」,用设计模式把变化隔离好,改起来就轻松多了。

公众号:蓝海资料掘金营,微信deep3321