重构与可扩展性:重构代码以支持未来功能扩展

说实话,我见过太多项目死在「扩展性」这三个字上。

刚上线时跑得飞快,需求一来就改不动了。加个新功能,要动七八个文件。改一个接口,测试全挂。你想想看,这种代码谁敢动?

我个人习惯是:写代码时就要想好,三个月后别人怎么加功能。这不是过度设计,这是职业素养。

什么是可扩展性?

可扩展性说白了就是:不改或少改已有代码,就能加新功能

我在项目中遇到过一种典型场景:一个订单处理系统,最开始只支持微信支付。后来要加支付宝,结果发现支付逻辑散落在十几个方法里。改一个地方,另外三个地方跟着崩。这就是典型的「不可扩展」。

可扩展性的核心原则:

  • 对扩展开放,对修改关闭(开闭原则)
  • 依赖抽象,不依赖具体实现
  • 单一职责:每个模块只做一件事

重构前的代码:硬编码的噩梦

先看一段典型的「不可扩展」代码。嗯,我敢打赌你见过类似的:

// 重构前:硬编码支付逻辑
public class OrderService {
    public void processPayment(String type) {
        if ("wechat".equals(type)) {
            // 微信支付逻辑
            System.out.println("调用微信支付API");
            System.out.println("记录微信支付日志");
            System.out.println("发送微信支付通知");
        } else if ("alipay".equals(type)) {
            // 支付宝支付逻辑
            System.out.println("调用支付宝API");
            System.out.println("记录支付宝日志");
            System.out.println("发送支付宝通知");
        }
        // 再加一种支付方式?继续加 else if
    }
}

这段代码有什么问题?

  • 加新支付方式要改 processPayment 方法
  • 每种支付方式的逻辑混在一起,没法单独测试
  • 日志、通知等公共逻辑被重复写

我曾经在一个电商项目里看到类似的代码,当时要加「银联支付」。改完上线后,微信支付突然报错——因为一个全局变量被不小心覆盖了。嗯,这就是硬编码的代价。

重构后的代码:策略模式 + 工厂模式

怎么改?我建议用策略模式把每种支付方式封装成独立的类。再加一个简单工厂来创建它们。

// 1. 定义支付策略接口
public interface PaymentStrategy {
    void pay(Order order);
}

// 2. 微信支付实现
public class WechatPay implements PaymentStrategy {
    @Override
    public void pay(Order order) {
        System.out.println("调用微信支付API");
        System.out.println("记录微信支付日志");
        System.out.println("发送微信支付通知");
    }
}

// 3. 支付宝支付实现
public class AlipayPay implements PaymentStrategy {
    @Override
    public void pay(Order order) {
        System.out.println("调用支付宝API");
        System.out.println("记录支付宝日志");
        System.out.println("发送支付宝通知");
    }
}

// 4. 工厂类:根据类型创建策略
public class PaymentFactory {
    public static PaymentStrategy getStrategy(String type) {
        switch (type) {
            case "wechat": return new WechatPay();
            case "alipay": return new AlipayPay();
            default: throw new IllegalArgumentException("未知支付类型");
        }
    }
}

// 5. 使用:OrderService 不再关心具体实现
public class OrderService {
    public void processPayment(String type, Order order) {
        PaymentStrategy strategy = PaymentFactory.getStrategy(type);
        strategy.pay(order);
    }
}

现在加新支付方式,只需要:

  1. 新建一个类实现 PaymentStrategy 接口
  2. PaymentFactory 里加一个 case

OrderService 一行代码都不用改。这就是可扩展性。

我的经验:工厂里的 switch 其实也可以用反射或注册表来消除。但小项目没必要过度设计。先让代码「能扩展」,再考虑「优雅地扩展」。

可扩展性的知识体系

下面这张图是我自己总结的「可扩展性重构路线图」。你想想看,从硬编码到可扩展,其实就三步:

可扩展性重构路线图 阶段一:识别痛点 硬编码的 if-else 链 修改一个功能影响全局 重复代码散落各处 阶段二:提取抽象 定义接口/抽象类 分离变与不变 单一职责拆分 阶段三:实现扩展 策略模式 工厂模式 依赖注入 关键设计原则 🔹 开闭原则:对扩展开放,对修改关闭 🔹 依赖倒置:依赖抽象接口,不依赖具体类 🔹 组合优于继承:用策略组合代替类继承 🔹 接口隔离:接口要小而专,不要大而全 🔹 最少知识:一个类只和它的直接朋友通信 🔹 好莱坞原则:别调用我,我会调用你

更复杂的场景:插件化架构

如果功能扩展点很多(比如一个报表系统要支持几十种图表类型),策略模式就不够用了。我建议用插件化架构

核心思路:

  • 定义一个插件接口
  • 每个插件独立打包(jar/dll)
  • 主程序通过配置文件或扫描目录加载插件
// 插件接口
public interface ReportPlugin {
    String getType();
    void generate(Data data);
}

// 主程序:动态加载插件
public class ReportEngine {
    private Map<String, ReportPlugin> plugins = new HashMap<>();
    
    public void loadPlugins(String pluginDir) {
        // 扫描目录,反射加载所有实现 ReportPlugin 的类
        // 注册到 plugins 中
    }
    
    public void generateReport(String type, Data data) {
        ReportPlugin plugin = plugins.get(type);
        if (plugin != null) {
            plugin.generate(data);
        }
    }
}

这样做的好处是:加新报表类型,只需要丢一个 jar 包进去。主程序不用改,甚至不用重启(配合热加载)。

避坑指南:我曾经在一个项目中过度使用插件化,每个小功能都做成插件。结果插件之间互相依赖,加载顺序混乱,调试起来痛不欲生。记住:插件化的粒度要适中。一个插件应该是一个完整的功能模块,不是一个方法。

可扩展性的代价

说实话,可扩展性不是免费的。它需要:

代价 说明
代码量增加 接口、工厂、策略类,比直接写 if-else 多不少代码
理解成本 新人看代码要理解设计模式,上手慢一些
调试难度 调用链变长,出问题时追踪路径更复杂

所以我的建议是:只在变化点做抽象。如果一个功能未来三个月内没有扩展的可能,就别用策略模式。YAGNI(You Ain't Gonna Need It)原则在这里同样适用。

总结:可扩展性重构的 checklist

最后,我整理了一个 checklist,每次重构前可以对照一下:

  1. 识别变化点:哪些功能未来可能增加变体?
  2. 提取接口:把变化的行为抽象成接口
  3. 封装实现:每个变体一个类,独立测试
  4. 工厂/DI:用工厂或依赖注入管理对象创建
  5. 验证:加一个新功能,看需要改几处代码

如果加一个新功能只需要新建一个类,然后在配置里加一行——恭喜你,重构成功了。

一个小技巧:重构时先写测试。把现有行为用测试固定下来,再动手改代码。这样改完后跑一遍测试,就知道有没有破坏已有功能。我每次重构都这么干,从来没翻过车。

可扩展性不是一蹴而就的。它是在一次次重构中慢慢养成的习惯。嗯,从今天开始,写代码时多问自己一句:「三个月后,别人怎么在这里加功能?」


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