33. 设计模式与代码重构:用设计模式消除重复代码,改善可维护性,实际重构案例

说实话,我面试过不少候选人,简历上写着「精通设计模式」,但一聊到实际重构,就只会背那几个模式的定义。真正让我眼前一亮的,是那些能指着一段烂代码说:「这里用策略模式重构,能砍掉一半重复代码」的人。

今天咱们就聊点实在的——怎么用设计模式去消除重复代码,改善可维护性。我会拿一个我实际重构过的案例来讲,你跟着走一遍,应该能感受到那种「代码突然变清爽了」的快感。

先看一段「坏味道」代码

假设我们有个订单处理系统。不同订单类型,处理逻辑不一样。我见过有人这么写:

class OrderProcessor {
public:
    void process(Order& order) {
        if (order.type() == "normal") {
            // 普通订单:检查库存、扣减、发货
            checkStock(order);
            deductStock(order);
            ship(order);
        } else if (order.type() == "gift") {
            // 礼品订单:检查库存、扣减、包装、发货
            checkStock(order);
            deductStock(order);
            wrapGift(order);
            ship(order);
        } else if (order.type() == "preorder") {
            // 预购订单:检查库存、扣减、标记预购、发货
            checkStock(order);
            deductStock(order);
            markPreorder(order);
            ship(order);
        }
        // 以后每加一种类型,这里就多一个 else if
    }
};

这段代码有什么问题?你一眼就能看出来:重复。每个分支里都有 checkStockdeductStockship。而且,如果未来要加「国际订单」「团购订单」,这个函数会膨胀到没法看。

我在项目中遇到过类似的情况,当时那个文件已经 2000 多行了。每次改一个逻辑,都得小心翼翼,生怕漏掉某个分支。嗯,这就是典型的「坏味道」——重复的 if-else 结构

用策略模式消除重复

为什么会这样?说白了,就是不同的处理逻辑被硬编码在同一个函数里了。我们需要的,是把「变化的部分」抽出来,让「不变的部分」复用。

策略模式正好干这个事。它把每个算法封装成独立的类,让它们可以互相替换。来看看重构后的样子:

// 策略接口
class OrderStrategy {
public:
    virtual ~OrderStrategy() = default;
    virtual void execute(Order& order) = 0;
};

// 具体策略:普通订单
class NormalOrderStrategy : public OrderStrategy {
public:
    void execute(Order& order) override {
        checkStock(order);
        deductStock(order);
        ship(order);
    }
};

// 具体策略:礼品订单
class GiftOrderStrategy : public OrderStrategy {
public:
    void execute(Order& order) override {
        checkStock(order);
        deductStock(order);
        wrapGift(order);
        ship(order);
    }
};

// 具体策略:预购订单
class PreorderStrategy : public OrderStrategy {
public:
    void execute(Order& order) override {
        checkStock(order);
        deductStock(order);
        markPreorder(order);
        ship(order);
    }
};

// 上下文类
class OrderProcessor {
public:
    void process(Order& order) {
        auto strategy = getStrategy(order.type());
        strategy->execute(order);
    }

private:
    std::unique_ptr<OrderStrategy> getStrategy(const std::string& type) {
        if (type == "normal") return std::make_unique<NormalOrderStrategy>();
        if (type == "gift")   return std::make_unique<GiftOrderStrategy>();
        if (type == "preorder") return std::make_unique<PreorderStrategy>();
        throw std::runtime_error("unknown order type");
    }
};

你看,OrderProcessor::process 现在只有三行。每个策略类只关心自己的特殊逻辑。以后加新类型,只需要新增一个策略类,改一下 getStrategy 工厂方法就行。原来的代码不用动。

核心思想:把「变」与「不变」分离。不变的流程(检查库存、扣减、发货)可以提取到基类或模板方法里,变的细节(包装、标记)交给子类。

再进一步:用模板方法模式消除策略内的重复

你仔细看上面的策略类,每个策略里都有 checkStockdeductStockship 这三步。这不又重复了吗?

嗯,这里可以用模板方法模式来优化。把公共流程放到基类,让子类只实现差异部分:

class OrderStrategy {
public:
    virtual ~OrderStrategy() = default;

    // 模板方法:定义了算法骨架
    void execute(Order& order) {
        checkStock(order);
        deductStock(order);
        extraProcess(order);  // 子类实现
        ship(order);
    }

protected:
    virtual void extraProcess(Order& order) = 0;

private:
    void checkStock(Order& order) { /* 公共实现 */ }
    void deductStock(Order& order) { /* 公共实现 */ }
    void ship(Order& order) { /* 公共实现 */ }
};

class GiftOrderStrategy : public OrderStrategy {
protected:
    void extraProcess(Order& order) override {
        wrapGift(order);
    }
};

class PreorderStrategy : public OrderStrategy {
protected:
    void extraProcess(Order& order) override {
        markPreorder(order);
    }
};

这样一来,重复的公共代码被彻底消除。每个子类只需要写自己那一点点特殊逻辑。我个人习惯把这种组合叫做「策略+模板方法」双模式,对付这种「流程固定、细节多变」的场景特别好用。

重构的收益:可维护性大幅提升

重构完以后,你想想看,如果产品经理说:「我们要加一个国际订单,需要额外做汇率转换和海关申报。」

你只需要:

  1. 新建一个 InternationalOrderStrategy,继承 OrderStrategy
  2. extraProcess 里实现汇率转换和海关申报
  3. getStrategy 里加一个分支

原来的代码一行都不用改。这就是开闭原则——对扩展开放,对修改关闭。

避坑指南:我曾经在重构时犯过一个错——把所有策略类都塞进一个文件里。结果文件又变成了 1000 多行。记住,每个策略类单独一个文件,命名清晰,比如 gift_order_strategy.h。这样找代码、改代码都方便。

知识体系:设计模式与重构的关系

为了让你更直观地理解,我画了一张图,展示设计模式在重构中的定位:

设计模式在代码重构中的定位 代码坏味道 重复的 if-else 长函数 散弹式修改 识别 重构手法 提取类 组合取代继承 多态取代条件 应用 设计模式 策略模式 模板方法 工厂方法 重构收益 消除重复代码 → 可维护性提升 遵循开闭原则 → 扩展性增强 单一职责 → 代码易测试

这张图想表达的是:坏味道 → 重构手法 → 设计模式 → 收益。你不需要记住所有模式,但你要能识别出代码里的坏味道,然后知道用哪种模式去解决。

实际重构中的几个建议

最后,分享几个我这些年重构踩过的坑:

  • 不要一次性重构太多。我曾经试图在一个大版本里把所有 if-else 都改成策略模式,结果改到一半需求变了,代码变得不伦不类。建议每次只重构一个模块,测试通过后再继续。
  • 先写测试,再重构。没有测试的保护,重构就是裸奔。我习惯先用单元测试把现有行为锁定,然后放心大胆地改。
  • 模式不是银弹。如果只有两三种分支,用 if-else 反而更清晰。别为了用模式而用模式,那叫过度设计。
  • 注意策略类的生命周期。如果策略类是无状态的,可以做成单例,避免每次创建新对象。我在一个高并发系统里就吃过这个亏——频繁 new 策略对象导致 GC 压力大。

警告:重构时最容易犯的错是「改着改着就偏离了原始行为」。我曾经把一个订单处理逻辑重构后,发现礼品订单不再扣减库存了——因为我在提取公共流程时漏掉了 deductStock。所以,重构后一定要跑一遍所有测试用例,尤其是边界情况。

好了,关于设计模式与代码重构,今天就聊这么多。记住,模式是工具,不是目的。你的目标是写出容易改、不容易出 bug 的代码。下次你看到一段重复的 if-else,不妨想想:这里能不能用策略模式?能不能用模板方法?


专注资料整理