工厂方法模式(Factory Method Pattern)

工厂方法模式(Factory Method Pattern)

前言

简单工厂模式虽然初步实现了对象创建与使用的分离,但其内部臃肿的 if-else 逻辑却成为了系统扩展时的痛点,严重违背了开闭原则(OCP)

如何优雅地打破这种"上帝类"的束缚?工厂方法模式(Factory Method Pattern) 给出了标准答案:利用多态,将对象的实例化彻底延迟到子类中进行。

参考博客:工厂方法设计模式

一、核心定义

简单工厂模式 中,所有的创建逻辑都集中在一个"上帝工厂类"里(充满了 if-elseswitch-case)。

痛点 :一旦新增产品(比如新增华为手机),就必须修改这个工厂类的源代码,这严重违背了开闭原则(Open-Closed Principle, OCP)------对扩展开放,对修改封闭。

工厂方法模式 通过"多态"解决了这个问题:将对象的实例化推迟到子类中进行。

核心思想是:定义一个创建对象的接口(抽象工厂),但让实现这个接口的类(具体工厂)来决定实例化哪个类(具体产品)。

核心思想 :不直接 new 对象,而是通过工厂子类来决定创建哪种产品,实现创建与使用的解耦

作用 说明
解耦 客户端代码只依赖抽象接口,不依赖具体产品类,降低耦合度
开闭原则 新增产品只需新增子类工厂,不需要修改已有代码
单一职责 将产品创建逻辑集中在工厂类中,业务代码不关心对象如何创建
灵活扩展 支持运行时动态切换产品族,适应不同平台/环境的需求

适用场景

  • 不确定运行时需要哪种具体对象
  • 希望通过子类来扩展产品创建逻辑
  • 需要将创建逻辑与业务逻辑分离

二、标准体系结构图 (UML)

工厂方法模式包含四个核心角色:

  1. 抽象产品(Product):定义产品的接口规范。
  2. 具体产品(Concrete Product):实现抽象产品接口的具体类。
  3. 抽象工厂(Abstract Factory):声明返回抽象产品对象的工厂方法。
  4. 具体工厂(Concrete Factory):实现工厂方法,返回具体的实例。

实现 (Implements)
实现 (Implements)
实例化 (Creates)
依赖 (Depends)
<<interface>>
Product
+doSomething() : void
ConcreteProduct
+doSomething() : void
<<interface>>
AbstractFactory
+factoryMethod() : Product
ConcreteFactory
+factoryMethod() : Product

三、 场景推演:手机代工厂

基础代码实现(以手机代工厂为例):

Java 复制代码
// 1. 抽象产品:手机
public interface Phone {
    void make();
}

// 2. 具体产品:苹果手机与小米手机
public class IPhone implements Phone {
    @Override
    public void make() { System.out.println("组装一台 iPhone"); }
}
public class MiPhone implements Phone {
    @Override
    public void make() { System.out.println("组装一台 小米手机"); }
}

// 3. 抽象工厂:手机制造标准
public interface PhoneFactory {
    Phone createPhone(); // 将生产细节推迟到具体工厂
}

// 4. 具体工厂:专厂专办
public class AppleFactory implements PhoneFactory {
    @Override
    public Phone createPhone() { return new IPhone(); }
}
public class XiaomiFactory implements PhoneFactory {
    @Override
    public Phone createPhone() { return new MiPhone(); }
}

客户端调用:

Java 复制代码
public class Client {
    public static void main(String[] args) {
        // 想买小米手机,找小米专属工厂
        PhoneFactory xiaomiFactory = new XiaomiFactory();
        Phone myPhone = xiaomiFactory.createPhone();
        myPhone.make(); 
        
        // 【优势体现】:如果明天要造华为手机,只需新增 HuaweiPhone 和 HuaweiFactory 类,
        // 原有代码一行都不用改,完美符合开闭原则!
    }
}

四、实战案例:跨平台对话框按钮

4.1 需求分析

业务场景:开发一个跨平台的对话框组件,根据当前运行环境(Windows / Web)自动渲染不同风格的按钮:

  • Windows 环境:渲染原生 Swing 窗口按钮(带 GUI 面板、标签、退出按钮)
  • Web 环境 :渲染 HTML 按钮(输出 <button> 标签)

痛点 :如果用 if-else 在业务代码里判断环境并直接 new 不同的按钮对象,会导致:

  1. 客户端代码与所有具体按钮类强耦合
  2. 每新增一种平台按钮就要修改业务逻辑,违反开闭原则
  3. 创建逻辑散落在各处,难以维护

解决方案 :使用工厂方法模式,将按钮的创建委托给各平台的工厂子类。

**实战代码:**https://github.com/likerhood/CodeDesignWork

运行结果:

4.2 架构图

4.2.1 面条代码架构图
4.2.2 工厂方法架构图

4.3 类图对比

4.3.1 面条代码类图

调用
直接依赖
直接依赖
直接依赖
直接依赖
Client
+main(args: String[]) : void
Dialog
+renderWindow() : void
+showAlert() : void
HtmlButtons
+render() : void
+onClick() : void
WindowsButtons
+render() : void
+onClick() : void
MacButtons
+render() : void
+onClick() : void
LinuxButtons
+render() : void
+onClick() : void

4.3.2 工厂方法类图

实现
实现
继承
继承
创建
创建
创建
依赖
<<interface>>
IButton
+render() : void
+onClick() : void
HtmlButtons
+render() : void
+onClick() : void
WindowsButtons
-panel: JPanel
-frame: JFrame
-button: JButton
+render() : void
+onClick() : void
<<abstract>>
DialogFactory
+renderWindow() : void
+createButton() : IButton
HtmlDialogFactory
+createButton() : IButton
WindowsDialogFactory
+createButton() : IButton
Client
-dialogFactory: DialogFactory
+config() : void
+runBusinessLogic() : void

4.4 时序图

4.4.2 面条代码类图

HtmlButtons WindowsButtons Dialog(上帝类) 客户端 HtmlButtons WindowsButtons Dialog(上帝类) 客户端 alt [os == "Windows 11"] [os == "Mac OS X"] [其他] 所有判断逻辑 都堆在这一个方法里 renderWindow() 获取 os.name new WindowsButtons() render() new MacButtons()... new HtmlButtons() render()

4.4.2 工厂方法类图

WindowsButtons WindowsDialogFactory DialogFactory 客户端(DialogFactoryTest) WindowsButtons WindowsDialogFactory DialogFactory 客户端(DialogFactoryTest) alt [Windows 环境] [非 Windows 环境] config() 检测操作系统 new WindowsDialogFactory() new HtmlDialogFactory() renderWindow() createButton() [工厂方法] 返回 IButton 实例 render() onClick() 渲染完成 业务执行完成

4.5 代码比较

4.5.1 工厂方法代码

代码结构

复制代码
com.likerhood.design
├── buttons/                      # 产品层
│   ├── IButton.java              # 产品接口
│   └── imp/
│       ├── HtmlButtons.java      # 具体产品 - HTML按钮
│       └── WindowsButtons.java   # 具体产品 - Windows按钮
├── factory/                      # 工厂层
│   ├── DialogFactory.java        # 抽象工厂(含工厂方法)
│   └── imp/
│       ├── HtmlDialogFactory.java      # 具体工厂 - 生产HTML按钮
│       └── WindowsDialogFactory.java   # 具体工厂 - 生产Windows按钮

产品接口 IButton:定义按钮的统一行为契约

java 复制代码
public interface IButton {
    void render();   // 渲染按钮
    void onClick();  // 点击事件
}

抽象工厂 DialogFactory:定义工厂方法 + 业务模板

java 复制代码
public abstract class DialogFactory {
    // 业务方法:渲染对话窗口(模板方法)
    public void renderWindow() {
        IButton okButton = createButton();  // 调用工厂方法
        okButton.render();
    }

    // 工厂方法:由子类实现,决定创建哪种按钮
    public abstract IButton createButton();
}

具体工厂:各自创建对应平台的按钮

java 复制代码
// HTML 工厂
public class HtmlDialogFactory extends DialogFactory {
    public IButton createButton() {
        return new HtmlButtons();
    }
}

// Windows 工厂
public class WindowsDialogFactory extends DialogFactory {
    public IButton createButton() {
        return new WindowsButtons();
    }
}

客户端:运行时根据环境选择工厂

java 复制代码
public static void config() {
    if (System.getProperty("os.name").equals("Windows 11")) {
        dialogFactory = new WindowsDialogFactory();
    } else {
        dialogFactory = new HtmlDialogFactory();
    }
}

public static void runBusinessLogic() {
    dialogFactory.renderWindow();  // 客户端只依赖抽象,不关心具体产品
}
4.5.2 面条代码(if-else 硬编码)

如果不用工厂方法模式,最直接的做法就是把所有创建逻辑和业务逻辑混在一起 ,用 if-else 判断环境后直接 new 具体对象:

java 复制代码
public class Dialog {

    public void renderWindow() {
        String os = System.getProperty("os.name");

        if (os.equals("Windows 11")) {
            // 直接 new 具体产品类
            WindowsButtons button = new WindowsButtons();
            button.render();
        } else {
            // 直接 new 另一个具体产品类
            HtmlButtons button = new HtmlButtons();
            button.render();
        }
    }
}

客户端调用:

java 复制代码
public class Client {
    public static void main(String[] args) {
        Dialog dialog = new Dialog();
        dialog.renderWindow();
    }
}

看起来代码更少,但问题巨大。

如果需求变化了呢?

假设现在要新增 Mac 按钮Linux 按钮,代码会变成这样:

java 复制代码
public class Dialog {

    public void renderWindow() {
        String os = System.getProperty("os.name");

        if (os.equals("Windows 11")) {
            WindowsButtons button = new WindowsButtons();
            button.render();
        } else if (os.equals("Mac OS X")) {
            MacButtons button = new MacButtons();    // 改动1
            button.render();
        } else if (os.equals("Linux")) {
            LinuxButtons button = new LinuxButtons(); // 改动2
            button.render();
        } else {
            HtmlButtons button = new HtmlButtons();
            button.render();
        }
    }

    // 假设还有另一个业务方法也需要按钮...
    public void showAlert() {
        String os = System.getProperty("os.name");

        // 又要重复一遍完全相同的 if-else!
        if (os.equals("Windows 11")) {
            WindowsButtons button = new WindowsButtons();
            button.onClick();
        } else if (os.equals("Mac OS X")) {
            MacButtons button = new MacButtons();
            button.onClick();
        } else if (os.equals("Linux")) {
            LinuxButtons button = new LinuxButtons();
            button.onClick();
        } else {
            HtmlButtons button = new HtmlButtons();
            button.onClick();
        }
    }
}

每新增一个平台,所有用到按钮的地方都要改,if-else 到处重复。


总结

角色 本案例对应类 职责
抽象产品 IButton 定义产品的统一接口
具体产品 HtmlButtons / WindowsButtons 实现不同平台的按钮逻辑
抽象工厂 DialogFactory 声明工厂方法 + 封装业务模板
具体工厂 HtmlDialogFactory / WindowsDialogFactory 实现工厂方法,创建对应产品
客户端 DialogFactoryTest 选择工厂,调用业务方法

工厂方法模式的关键点

  • DialogFactory.renderWindow() 中调用了抽象方法 createButton(),具体创建哪种按钮由子类工厂决定------这就是"将实例化延迟到子类"的精髓。

  • 客户端只与 DialogFactoryIButton 两个抽象打交道,完全不感知具体产品类的存在,实现了真正的面向接口编程

相关推荐
Rsun045513 天前
3、Java 工厂方法模式从入门到实战
java·开发语言·工厂方法模式
妙蛙种子31113 天前
【Java设计模式 | 创建者模式】工厂方法模式
java·后端·设计模式·工厂方法模式
yaaakaaang18 天前
二、工厂方法模式
java·工厂方法模式
无籽西瓜a20 天前
【西瓜带你学设计模式 | 第三期-工厂方法模式】工厂方法模式——定义、实现方式、优缺点与适用场景以及注意事项
java·后端·设计模式·工厂方法模式
不秃不少年20 天前
工厂方法模式(Factory Method)
java·面试·工厂方法模式
bmseven24 天前
23种设计模式 - 工厂方法(Factory Method)
设计模式·工厂方法模式
砍光二叉树1 个月前
【设计模式】创建型-工厂方法模式
设计模式·工厂方法模式
易水寒陈1 个月前
单片机的工厂方法模式和桥接模式结合使用
单片机·桥接模式·工厂方法模式
夕珩1 个月前
单例模式、原型模式、工厂方法模式、抽象工厂模式、建造者模式、解释器模式、命令模式
单例模式·解释器模式·建造者模式·工厂方法模式·抽象工厂模式·命令模式·原型模式