一、从简单工厂说起

讲工厂方法模式之前,我得先聊聊简单工厂。说实话,简单工厂并不是GoF 23种设计模式之一,但它却是理解工厂方法最好的敲门砖。

简单工厂长什么样?说白了就是:一个工厂类,根据参数决定创建哪个产品。我刚开始写Java那会儿,特别喜欢这么干——把所有创建逻辑塞到一个类里,调用方传个字符串或者枚举,工厂就给你返回对应的对象。

举个例子,假设我们要做一个日志记录器:

// 产品接口
public interface Logger {
    void log(String message);
}

// 具体产品
public class FileLogger implements Logger {
    public void log(String message) {
        System.out.println("写入文件: " + message);
    }
}

public class ConsoleLogger implements Logger {
    public void log(String message) {
        System.out.println("控制台输出: " + message);
    }
}

// 简单工厂
public class LoggerFactory {
    public static Logger createLogger(String type) {
        if ("file".equals(type)) {
            return new FileLogger();
        } else if ("console".equals(type)) {
            return new ConsoleLogger();
        }
        throw new IllegalArgumentException("未知的日志类型");
    }
}

嗯,这段代码看起来挺清爽的,对吧?但我在项目中遇到过一个问题:每次新增一种日志类型,都得改LoggerFactory的代码。这违反了开闭原则——对扩展开放,对修改关闭。

⚠️ 简单工厂的致命伤:当产品种类增多,工厂类会变得臃肿不堪。而且每次新增产品都要修改工厂类,容易引发回归问题。

二、工厂方法模式的定义

那怎么解决简单工厂的痛点呢?工厂方法模式给出了答案。

工厂方法模式的定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

你想想看,这句话的核心是什么?把「创建对象」这个动作,从父类下放到子类。父类只定义「怎么用」,子类决定「用哪个」。

🎯 核心思想:不再用一个万能工厂包揽所有创建逻辑,而是为每一种产品配备一个专门的工厂。每个工厂只负责生产一种产品。

三、模式结构

工厂方法模式的结构其实很清晰,我画了一张图帮你理解:

工厂方法模式结构图 抽象工厂 Factory 具体工厂1 ConcreteFactory1 具体工厂2 ConcreteFactory2 具体工厂3 ConcreteFactory3 抽象产品 Product 具体产品1 ConcreteProduct1 具体产品2 ConcreteProduct2 具体产品3 ConcreteProduct3 抽象层 具体工厂 具体产品

结构中的角色就四个:

  • 抽象工厂(Factory):声明工厂方法,返回一个产品对象
  • 具体工厂(ConcreteFactory):实现工厂方法,创建具体的产品
  • 抽象产品(Product):定义产品的接口
  • 具体产品(ConcreteProduct):实现产品接口的具体类

四、代码实现

还是用日志记录器的例子,我们用工厂方法模式重写一遍:

// 1. 抽象产品
public interface Logger {
    void log(String message);
}

// 2. 具体产品
public class FileLogger implements Logger {
    public void log(String message) {
        System.out.println("写入文件: " + message);
    }
}

public class ConsoleLogger implements Logger {
    public void log(String message) {
        System.out.println("控制台输出: " + message);
    }
}

// 3. 抽象工厂
public abstract class LoggerFactory {
    public abstract Logger createLogger();
    
    // 这里可以加一些公共逻辑
    public void logMessage(String message) {
        Logger logger = createLogger();
        logger.log(message);
    }
}

// 4. 具体工厂
public class FileLoggerFactory extends LoggerFactory {
    public Logger createLogger() {
        return new FileLogger();
    }
}

public class ConsoleLoggerFactory extends LoggerFactory {
    public Logger createLogger() {
        return new ConsoleLogger();
    }
}

// 5. 客户端使用
public class Client {
    public static void main(String[] args) {
        LoggerFactory factory = new FileLoggerFactory();
        factory.logMessage("这是一条文件日志");
        
        factory = new ConsoleLoggerFactory();
        factory.logMessage("这是一条控制台日志");
    }
}
💡 我的习惯:在抽象工厂里,我通常会加一个模板方法(比如上面的logMessage),把「创建+使用」的逻辑封装在一起。这样客户端只需要关心用哪个工厂,不用管创建和调用的细节。

五、优缺点分析

✅ 优点 ❌ 缺点
  • 符合开闭原则:新增产品时,只需添加新的具体工厂和具体产品,无需修改现有代码
  • 符合单一职责:每个工厂只负责创建一种产品,职责清晰
  • 解耦:客户端只依赖抽象工厂和抽象产品,不依赖具体实现
  • 易于扩展:我曾在项目中用工厂方法模式对接过三种不同的消息队列,每种MQ一个工厂,互不干扰
  • 类数量暴增:每增加一个产品,就要增加一个具体工厂和一个具体产品。产品多了,类会非常多
  • 增加系统复杂度:对于简单的创建逻辑,用工厂方法反而显得过度设计
  • 抽象层增加理解难度:新手看到一堆Factory和Product,容易懵
⚠️ 我曾经踩过的坑:有一次在一个小工具里用了工厂方法模式,结果就两种产品,硬是写了6个类。后来同事接手时吐槽说「这代码过度设计了」。所以我的建议是:产品种类少于3个时,用简单工厂就够了;超过3个且可能继续扩展时,再考虑工厂方法

六、什么时候用工厂方法?

我个人总结了三个适用场景:

  1. 客户端不知道它需要创建哪个具体类——比如框架代码,由子类决定实例化哪个类
  2. 一个类希望由它的子类来指定创建的对象——父类定义行为框架,子类填充具体实现
  3. 将创建逻辑从使用逻辑中分离出来——说白了就是解耦,让创建和使用各司其职

你想想看,其实很多框架都在用工厂方法模式。比如Spring的BeanFactory,虽然它用的是更高级的抽象,但底层思想是一样的——把「创建对象」这件事抽象出来,交给专门的组件去处理

📌 一句话总结:工厂方法模式,就是用继承的方式,把「创建对象」这个变化点封装起来。当你的系统需要支持多种产品,且产品种类可能不断扩展时,它就是你的最佳选择。

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