大型重构(一):梳理并分解继承体系、将过程化设计转化为对象设计

说实话,我见过太多代码库,一开始只是个小项目,大家图快,用过程化的方式堆功能。结果呢?三年后,一个函数上千行,if-else 嵌套七八层,新来的同事看一眼就想跑。这时候,你就得动大手术了——大型重构。

今天咱们聊两个最核心的“大手术”:梳理并分解继承体系,以及将过程化设计转化为对象设计。这两个活儿,我职业生涯里干过不下十次,每次都是硬仗。

为什么继承体系会变成一团乱麻?

我刚开始做设计时,觉得继承是面向对象最牛的特性。后来发现,滥用继承比不用继承更可怕。你想想看,一个类继承自另一个类,子类又继承自子类,五层下去,谁是谁的爹都搞不清了。

我在项目中遇到过这样一个案例:一个叫 Order 的基类,下面有 SpecialOrderVIPOrderInternationalOrder,然后 VIPOrder 又派生出 GoldVIPOrderPlatinumVIPOrder。每个子类都重写了父类的方法,但重写逻辑里又调用了 super 方法。结果呢?改一个父类方法,五个子类全崩。

说白了,继承体系混乱的根源就两个:

  • 职责不清:一个类干了太多事,导致子类不得不覆盖大量方法
  • 层次过深:超过三层的继承,基本就是灾难

分解继承体系的三个步骤

我个人习惯,拿到一团乱麻的继承体系,先做三件事:

  1. 找出“上帝类”——那个被最多子类继承的基类,往往就是问题核心
  2. 识别“变与不变”——哪些行为是稳定的?哪些行为每个子类都不一样?
  3. 用组合替代继承——把变化的行为提取成接口或策略类

举个例子,咱们看一段典型的“坏味道”代码:

// 过程化风格,一个类处理所有订单类型
public class OrderProcessor {
    public double calculateTotal(Order order) {
        if (order.getType().equals("NORMAL")) {
            return order.getAmount() * 0.9; // 普通订单9折
        } else if (order.getType().equals("VIP")) {
            return order.getAmount() * 0.8; // VIP订单8折
        } else if (order.getType().equals("INTERNATIONAL")) {
            double base = order.getAmount() * 0.85;
            return base + base * 0.1; // 国际订单加关税
        }
        // 再加一种类型?继续加 else if
        return order.getAmount();
    }
}

这段代码,说白了就是披着面向对象外衣的过程化设计。每加一种订单类型,就得改这个函数。我见过最夸张的,这种 if-else 链写了三十多个分支。

重构之后,应该是这样的:

// 策略模式,每个策略独立
public interface DiscountStrategy {
    double calculate(double amount);
}

public class NormalDiscount implements DiscountStrategy {
    public double calculate(double amount) {
        return amount * 0.9;
    }
}

public class VIPDiscount implements DiscountStrategy {
    public double calculate(double amount) {
        return amount * 0.8;
    }
}

public class InternationalDiscount implements DiscountStrategy {
    public double calculate(double amount) {
        double base = amount * 0.85;
        return base + base * 0.1;
    }
}

// 订单类只依赖策略接口
public class Order {
    private DiscountStrategy strategy;
    
    public double getTotal() {
        return strategy.calculate(this.amount);
    }
}

嗯,这里要注意:不要一上来就全盘重写。我曾经犯过这个错,花了两周重构,结果上线后一堆 bug。正确的做法是:一次只提取一个策略,每提取一个就跑一遍测试。

过程化设计转对象设计:从“怎么做”到“谁来做”

过程化设计的核心思维是“先做什么,再做什么”。对象设计的核心思维是“谁负责做什么”。

我举个例子,你感受一下区别。假设有个报表生成功能:

// 过程化:一个函数搞定所有
public class ReportGenerator {
    public void generateReport(String type) {
        // 1. 查询数据
        List<Data> data = queryData();
        
        // 2. 格式化数据
        String formatted = "";
        if (type.equals("CSV")) {
            formatted = formatAsCSV(data);
        } else if (type.equals("PDF")) {
            formatted = formatAsPDF(data);
        }
        
        // 3. 发送邮件
        sendEmail(formatted);
    }
}

这个函数干了三件事:查数据、格式化、发邮件。每件事都耦合在一起。如果哪天要加个微信通知,你怎么办?继续往这个函数里塞?

重构后,我们把职责拆开:

// 每个类只做一件事
public class DataFetcher {
    public List<Data> fetch() { /* 查询数据 */ }
}

public interface Formatter {
    String format(List<Data> data);
}

public class CSVFormatter implements Formatter { /* CSV格式化 */ }
public class PDFFormatter implements Formatter { /* PDF格式化 */ }

public class Notifier {
    public void send(String content) { /* 发送通知 */ }
}

// 协调者只负责编排
public class ReportService {
    private DataFetcher fetcher;
    private Formatter formatter;
    private Notifier notifier;
    
    public void generateReport() {
        List<Data> data = fetcher.fetch();
        String content = formatter.format(data);
        notifier.send(content);
    }
}

你看,现在每个类都只关心自己的事。加一种通知方式?加一个 Notifier 的子类就行。加一种格式?加一个 Formatter 的实现类。完全不用改现有代码。

避坑指南

我曾经在重构一个支付系统时,把过程化代码全拆成了对象。结果拆得太细,一个支付流程涉及十几个类,调用链特别长,调试起来想死的心都有。

所以,这里给你三个建议:

  • 不要过度设计:如果某个逻辑未来两年内都不会变,别急着拆。拆了反而增加复杂度。
  • 保持接口稳定:一旦把行为提取成接口,就不要轻易改接口签名。否则所有实现类都得跟着改。
  • 先写测试再重构:没有测试覆盖的代码,重构就是裸奔。我吃过这个亏,现在每次重构前,先补测试。

一张图看懂重构思路

下面这张图,是我做这类重构时脑子里始终绷着的一根弦。左边是混乱的继承和过程化代码,右边是清晰的对象设计:

重构前:混乱的继承 + 过程化 上帝类(BaseOrder) NormalOrder VIPOrder InternationalOrder if (type == "NORMAL") { ... } else if (type == "VIP") { ... } else if (type == "INTERNATIONAL") { ... } // 再加一种?继续加 else if 重构后:组合 + 策略模式 Order(持有策略) DiscountStrategy(接口) NormalDiscount VIPDiscount InternationalDiscount ✅ 新增策略无需改现有代码 ✅ 每个类只负责一件事 ✅ 继承深度不超过2层 ✅ 测试可以针对单个策略写

总结一下

大型重构,说白了就是两件事:把混乱的继承体系拆成组合把过程化的逻辑装进对象里。做的时候记住三点:

  • 一次只改一个点,改完就跑测试
  • 用策略模式替代 if-else 链
  • 组合优于继承,接口优于抽象类

嗯,今天就聊到这儿。下次咱们接着聊大型重构的另一个重头戏——如何安全地拆分一个巨型类。

核心要点:重构不是重写。别想着一步到位,每次改动都要保证系统还能跑。我见过太多重构到一半,项目黄了的案例。稳扎稳打,才是正道。

我的小技巧:每次重构前,先在代码里画一张“依赖图”。把类之间的继承、调用关系画出来。这张图能帮你一眼看出哪些类是“上帝类”,哪些是“孤魂野鬼”。

警告:如果你重构的代码没有单元测试,请先补测试。没有测试的重构,就像在雷区里跳舞。我曾经跳过,结果炸得满身是伤。

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