设计模式(二)工厂方法模式 — 把创建权限下放给子类,像“可扩展的生产线”

工厂方法模式(Factory Method)是 GoF 设计模式中最经典、最核心的创建型模式之一。

它的目标简单而深刻:
将对象创建延迟到子类,使得创建逻辑可扩展、可替换、可按需分派,就像一条能不断扩展的生产线


🏭 一、工厂方法模式解决什么问题?

传统做法:直接 new 一个具体类

java 复制代码
MessageSender sender = new EmailSender();

这会带来几个问题:

问题 描述
强依赖具体类 客户端依赖 EmailSender,一旦替换实现,就必须改客户端
创建逻辑分散 连接参数、构造参数散落在代码中,难维护
扩展不友好 想增加新的实现(如 PushSender),必须修改业务代码(违背 OCP)
配置难以驱动 想让创建逻辑由配置(YAML/DB)决定非常困难

✨ 工厂方法模式解决什么?

  • 创建逻辑集中管理
  • 接口隔离调用者与具体类
  • 让扩展变得简单:添加新产品 → 添加新工厂 → 客户端无需改动
  • 能非常自然地结合 Spring / SPI / 配置中心

🔑 二、工厂方法的核心思想:把"创建权"交给子类

工厂方法典型定义:

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

重点词:

  • "接口":约束创建
  • "子类":扩展入口
  • "延迟到子类":允许动态替换

工厂方法本质就是一个 可扩展创建点(Extension Point)


🧭 三、整体流程图(以工厂方法为例)

createProduct Client Factory ConcreteProduct

核心思想:客户端依赖工厂接口,而不是具体产品


🏷 四、UML 类图(工厂方法)

<<interface>> Product +use() ConcreteProductA +use() ConcreteProductB +use() <<abstract>> Factory +createProduct() ConcreteFactoryA +createProduct() ConcreteFactoryB +createProduct()


⏱ 五、时序图(客户端如何与工厂交互)

Client Factory Product createSender() new ConcreteSender() send() Client Factory Product


💡 六、示例场景:消息发送系统(Email / SMS)

我们以"多渠道消息发送"为例,适合作为工厂模式教学场景。

1)产品接口(Product)

java 复制代码
public interface MessageSender {
    void send(String to, String content);
}

2)具体产品实现 A(Email)

java 复制代码
public class EmailSender implements MessageSender {
    private final String smtpServer;

    public EmailSender(String smtpServer) {
        this.smtpServer = smtpServer;
    }

    @Override
    public void send(String to, String content) {
        System.out.println("[Email] to=" + to + " via " + smtpServer + " content=" + content);
        // 调用真实 SMTP 客户端逻辑...
    }
}

3)具体产品实现 B(SMS)

java 复制代码
public class SmsSender implements MessageSender {
    private final String provider;

    public SmsSender(String provider) {
        this.provider = provider;
    }

    @Override
    public void send(String to, String content) {
        System.out.println("[SMS] to=" + to + " via " + provider + " content=" + content);
        // 调用真实 SMS SDK...
    }
}

🧪 七、简单工厂(适合小项目)

java 复制代码
public class MessageSenderFactory {
    public static MessageSender create(String type) {
        switch (type) {
            case "EMAIL":
                return new EmailSender("smtp.example.com");
            case "SMS":
                return new SmsSender("sms-provider");
            default:
                throw new IllegalArgumentException("Unknown type: " + type);
        }
    }
}

优点:简单

缺点:新增类型要修改 switch → 违反开闭原则

客户端调用

java 复制代码
public class Main {
    public static void main(String[] args) {
        MessageSender email = MessageSenderFactory.create("EMAIL");
        email.send("alice@example.com", "Hello Alice");

        MessageSender sms = MessageSenderFactory.create("SMS");
        sms.send("+8613712345678", "Hello via SMS");
    }
}

🧩 八、工厂方法(Factory Method)实现

1)工厂接口

java 复制代码
public interface MessageFactory {
    MessageSender createSender();
}

2)具体工厂:EmailFactory

java 复制代码
public class EmailFactory implements MessageFactory {
    @Override
    public MessageSender createSender() {
        return new EmailSender("smtp.example.com");
    }
}

3)具体工厂:SmsFactory

java 复制代码
public class SmsFactory implements MessageFactory {
    @Override
    public MessageSender createSender() {
        return new SmsSender("sms-provider");
    }
}

4)客户端改造(依赖工厂)

java 复制代码
public class Client {
    private final MessageFactory factory;

    public Client(MessageFactory factory) {
        this.factory = factory;
    }

    public void doSend() {
        MessageSender sender = factory.createSender();
        sender.send("bob@example.com", "content");
    }

    public static void main(String[] args) {
        Client c = new Client(new EmailFactory());
        c.doSend();
    }
}

✔️ 扩展方式

新增 PushSender → 新建 PushFactory → 完全无需修改客户端


🏢 九、抽象工厂示例

1)抽象工厂与产品接口

java 复制代码
public interface Button {
    void render();
}

public interface Checkbox {
    void render();
}

public interface WidgetFactory {
    Button createButton();
    Checkbox createCheckbox();
}

2)具体工厂:LightThemeFactory / DarkThemeFactory

java 复制代码
public class LightButton implements Button {
    @Override
    public void render() {
        System.out.println("Render light button");
    }
}

public class DarkButton implements Button {
    @Override
    public void render() {
        System.out.println("Render dark button");
    }
}

public class LightFactory implements WidgetFactory {
    @Override
    public Button createButton() {
        return new LightButton();
    }
    @Override
    public Checkbox createCheckbox() {
        return () -> System.out.println("Render light checkbox");
    }
}

public class DarkFactory implements WidgetFactory {
    @Override
    public Button createButton() {
        return new DarkButton();
    }
    @Override
    public Checkbox createCheckbox() {
        return () -> System.out.println("Render dark checkbox");
    }
}

🧩 十、工厂方法 vs 简单工厂

简单工厂 工厂方法
扩展性 ❌ 修改 switch ✅ 增加类即可
是否遵守开闭原则 ❌ 不遵守 ✔️ 完全遵守
产品数量少 ✔️ 很适用 普通
产品扩展多 不适用 ✔️ 强推荐
是否由子类决定产品类型 是(关键差异)

🌿 十一、Spring 中的常见做法

1)把实现注册为 Bean 并注入 Map

java 复制代码
@Component("emailSender")
public class EmailSender implements MessageSender { ... }

@Component("smsSender")
public class SmsSender implements MessageSender { ... }

@Service
public class MessageService {
    private final Map<String, MessageSender> senders;
    @Autowired
    public MessageService(Map<String, MessageSender> senders) {
        this.senders = senders;
    }

    public void send(String type, String to, String content) {
        MessageSender sender = senders.get(type.toLowerCase());
        if (sender == null) throw new IllegalArgumentException("no sender");
        sender.send(to, content);
    }
}

2)注解 + 自动注册

自定义注解并通过 BeanPostProcessor 或 Spring 的 @Conditional 在启动时注册/筛选策略,适合复杂企业级场景(代码略,思路在于利用 Spring 扫描并把带注解的实现按注解值注册到 Map)。


⚠️十一、常见误区与反模式

误区 为什么错
把业务逻辑放进工厂 工厂只负责"创建",不负责"行为"
工厂方法用得太多 简单场景用简单工厂更合适
工厂类 new 来 new 去 工厂本身应该是单例/被容器管理
把工厂方法和抽象工厂混淆 抽象工厂是"产品族",工厂方法是"单一产品等级结构"
把策略模式当工厂 策略替换行为,工厂替换创建对象

🛠 十二、基于反射的可注册工厂(插件化)

1)注册中心工厂

java 复制代码
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class RegistryFactory {
    private static final Map<String, Class<? extends MessageSender>> REG = new ConcurrentHashMap<>();

    public static void register(String key, Class<? extends MessageSender> impl) {
        REG.put(key, impl);
    }

    public static MessageSender create(String key) {
        Class<? extends MessageSender> cls = REG.get(key);
        if (cls == null) throw new IllegalArgumentException("no impl");
        try {
            return cls.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

2)在实现类静态块中注册(或通过 SPI/反射扫描)

java 复制代码
public class EmailSender implements MessageSender {
    static {
        RegistryFactory.register("EMAIL", EmailSender.class);
    }
    @Override
    public void send(String to, String content) { ... }
}

注意:生产环境推荐通过模块初始化或框架扫描注册,避免类加载副作用。


🔚 十三、总结

  • 工厂模式 是创建型模式的基石,适合把"变化点(创建)"从业务中抽离。
  • 工厂有多种形式:简单工厂、工厂方法、抽象工厂,按复杂度与扩展性选择。
  • 与 Spring / SPI / DI 配合可做出强大的插件化、配置化创建体系。
  • 切记:工厂职责是"创建",不要把业务逻辑塞进去。
相关推荐
报错小能手2 小时前
C++笔记 bind函数模板
开发语言·c++·笔记
qq_12498707532 小时前
基于springboot的兴趣生活展示交流平台的设计与实现(源码+论文+部署+安装)
java·spring boot·生活·毕设
2501_941111522 小时前
C++中的适配器模式
开发语言·c++·算法
2501_941111942 小时前
C++中的适配器模式变体
开发语言·c++·算法
明洞日记2 小时前
【设计模式手册008】适配器模式 - 让不兼容的接口协同工作
java·设计模式·适配器模式
zzz海羊2 小时前
VSCode配置java中的lombok
java·开发语言·vscode
A-code2 小时前
Git 多模块项目管理
java·开发语言
TDengine (老段)2 小时前
TDengine 字符串函数 Replace 用户手册
java·大数据·数据库·物联网·时序数据库·tdengine·涛思数据
没头脑的男大2 小时前
Unet实现脑肿瘤分割检测
开发语言·javascript·ecmascript