22、重构与设计模式(上):策略模式、工厂模式在重构中的应用

设计模式这东西,很多人觉得是“高大上”的东西。其实不然。我做了十几年项目,越来越觉得设计模式就是重构的“目的地”。你想想看,代码写烂了,要改好,改着改着就发现——咦,这不就是某个模式吗?

今天咱们聊两个最常用的:策略模式和工厂模式。这两个模式在重构中出现的频率,我个人觉得能排进前三。

策略模式:把“如果-否则”拆成零件

先看一段代码。这种代码我见过无数次了:

public double calculatePrice(String userType, double amount) {
    if ("VIP".equals(userType)) {
        return amount * 0.8;
    } else if ("GOLD".equals(userType)) {
        return amount * 0.85;
    } else if ("SILVER".equals(userType)) {
        return amount * 0.9;
    } else {
        return amount;
    }
}

这段代码有什么问题?

  • 新增一个用户等级,就得改这个方法
  • 多个地方用到类似逻辑,就得复制粘贴
  • 测试起来很麻烦,要测所有分支

说白了,就是变化点没有隔离。每种计价方式都耦合在一起了。

核心思路:把每种计价方式封装成独立的策略类,让它们可以互相替换。

重构后的代码长这样:

// 策略接口
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 SilverDiscount implements DiscountStrategy {
    public double calculate(double amount) {
        return amount * 0.9;
    }
}

// 上下文类
public class PriceCalculator {
    private DiscountStrategy strategy;
    
    public PriceCalculator(DiscountStrategy strategy) {
        this.strategy = strategy;
    }
    
    public double calculate(double amount) {
        return strategy.calculate(amount);
    }
}

嗯,这里要注意:策略模式不是让你把所有if-else都干掉。它解决的是“行为有多种变体,且变体可能频繁变化”的问题。

我的经验:如果if-else只有两三个分支,而且几乎不变,就别硬套策略模式。过度设计比没有设计更可怕。

工厂模式:把“创建对象”这件事收口

工厂模式在重构中出现的频率,说实话比策略模式还高。为什么?因为对象的创建往往是耦合的源头

我曾经接手过一个项目,里面new对象的地方散落得到处都是。后来要换一个实现类,好家伙,改了二十多个文件。这就是典型的“创建逻辑分散”问题。

看个例子:

// 重构前:到处new
public class OrderService {
    public void processOrder(String type) {
        Payment payment;
        if ("ALIPAY".equals(type)) {
            payment = new Alipay();
        } else if ("WECHAT".equals(type)) {
            payment = new WechatPay();
        } else {
            payment = new CreditCard();
        }
        payment.pay();
    }
}

// 重构后:用简单工厂收口
public class PaymentFactory {
    public static Payment create(String type) {
        if ("ALIPAY".equals(type)) {
            return new Alipay();
        } else if ("WECHAT".equals(type)) {
            return new WechatPay();
        } else {
            return new CreditCard();
        }
    }
}

public class OrderService {
    public void processOrder(String type) {
        Payment payment = PaymentFactory.create(type);
        payment.pay();
    }
}

你看,改动不大,但效果很明显:

  • 创建逻辑集中到一处
  • 新增支付方式只改工厂类
  • 调用方不需要知道具体实现

注意:简单工厂不是真正的“工厂模式”,它只是一个封装手法。真正的工厂模式有工厂方法模式和抽象工厂模式,咱们后面会细讲。

策略+工厂:黄金搭档

在实际项目中,策略模式和工厂模式经常一起出现。策略模式负责“行为”,工厂模式负责“创建”。

我画了一张图,帮你理解它们的关系:

策略模式 + 工厂模式 协作关系 客户端 调用 策略工厂 创建 策略接口 策略A 策略B 策略C 工厂负责创建策略,客户端只依赖策略接口

这张图想表达什么?说白了就是:客户端不关心具体策略怎么来的,也不关心策略怎么实现的。它只跟策略接口打交道。

工厂负责把“创建”这件事收口。策略负责把“行为”这件事解耦。两者配合,代码的扩展性会好很多。

重构中的实战套路

我总结了一个简单的套路,你在重构时可以照着做:

  1. 找变化点:看看代码里哪些地方经常改,或者多个地方有类似的if-else
  2. 提取接口:把变化的行为抽象成接口
  3. 封装策略:每个分支变成一个策略类
  4. 引入工厂:把策略的创建集中到工厂里
  5. 替换调用:把原来的if-else替换成工厂+策略的组合

小技巧:不要一次性重构所有地方。先挑一个最乱的地方下手,改好了再扩散。我曾经一口气改了十几个文件,结果测试全崩了。后来学乖了,一次只改一个点。

什么时候不该用

说了这么多好处,也得说说坑。这两种模式不是银弹:

场景 建议
只有两三个分支,且几乎不变 别用,if-else更直接
策略类之间差异极小 考虑用参数化,别硬拆类
团队不熟悉设计模式 先简单封装,别引入太多概念
性能要求极高 策略模式有额外对象开销,注意

我曾经在一个性能敏感的项目里用了策略模式,结果每个请求都要new一堆策略对象,GC压力很大。后来改成了策略实例复用,才解决问题。嗯,这就是经验教训。

总结一下

策略模式和工厂模式,是重构中最常用的两个模式。它们解决的核心问题就两个:

  • 策略模式:把“怎么做”拆开,让行为可以替换
  • 工厂模式:把“怎么创建”收口,让创建逻辑集中

两者配合使用,代码的扩展性和可维护性会提升一个档次。但记住,模式是工具,不是目的。别为了用模式而用模式。

下一节咱们继续聊其他模式在重构中的应用,到时候再分享一些我在项目中踩过的坑。


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