当前位置: 首页 > news >正文

零基础设计模式——创建型模式 - 抽象工厂模式

第二部分:创建型模式 - 抽象工厂模式 (Abstract Factory Pattern)

我们已经学习了单例模式(保证唯一实例)和工厂方法模式(延迟创建到子类)。现在,我们来探讨创建型模式中更为复杂和强大的一个——抽象工厂模式。它处理的是创建“产品族”的问题。

  • 核心思想:提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们具体的类。

抽象工厂模式 (Abstract Factory Pattern)

“提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。”

想象一下,你要装修房子,可以选择不同的装修风格,比如“现代风格”、“中式风格”或“欧式风格”。

  • 每种风格都包含一系列配套的家具:沙发、茶几、电视柜等。
  • “现代风格”的家具(现代沙发、现代茶几)是一套,它们之间风格统一。
  • “中式风格”的家具(红木沙发、雕花茶几)是另一套,它们也风格统一。

你不会用一个现代风格的沙发去搭配一个中式雕花的茶几,这样会显得不伦不类。抽象工厂模式就是用来确保你得到的是一整套风格协调的产品。

1. 目的 (Intent)

抽象工厂模式的主要目的:

  1. 创建产品族:核心在于创建一系列相关的或相互依赖的对象(称为一个“产品族”)。例如,一个UI工具包工厂可能创建按钮、文本框、滚动条等一系列UI组件,这些组件需要有统一的外观和行为(如Windows风格或macOS风格)。
  2. 客户端与具体类解耦:客户端代码只与抽象的工厂接口和抽象的产品接口打交道,而不需要知道具体是哪个工厂的实现,也不需要知道具体的产品类名。
  3. 保证产品兼容性:由同一个具体工厂创建出来的产品,一定是相互兼容、可以协同工作的。

2. 生活中的例子 (Real-world Analogy)

  • 电脑组装

    • 抽象工厂 (AbstractFactory)ComputerPartsFactory (定义了创建CPU、主板、内存等组件的接口)。
    • 具体工厂 (ConcreteFactory)IntelCompatibleFactory (生产Intel CPU、兼容主板、特定内存条),AMDCompatibleFactory (生产AMD CPU、兼容主板、另一特定内存条)。
    • 抽象产品 (AbstractProduct)CPU, Motherboard, RAM (这些是组件的抽象接口)。
    • 具体产品 (ConcreteProduct)IntelCPU, AMDRyzenCPU, AsusMotherboard, GigabyteMotherboard, KingstonRAM, CorsairRAM
      当你选择 IntelCompatibleFactory 时,你会得到一套相互兼容的Intel平台组件。你不会得到一个Intel的CPU却配一个只支持AMD的主板。
  • 换肤功能 (Skinnable UI)

    • 抽象工厂UIThemeFactory (定义 createButton(), createCheckbox(), createWindow() 等方法)。
    • 具体工厂WindowsThemeFactory (创建Windows风格的按钮、复选框、窗口),MacThemeFactory (创建Mac风格的按钮、复选框、窗口),DarkThemeFactory (创建暗黑主题的组件)。
    • 抽象产品Button, Checkbox, Window (UI组件的接口)。
    • 具体产品WindowsButton, MacButton, DarkButton 等。
      用户选择一个主题(比如“暗黑主题”),应用就会使用 DarkThemeFactory 来创建所有UI元素,确保界面风格统一。

3. 结构 (Structure)

抽象工厂模式通常包含以下角色:

  1. AbstractFactory (抽象工厂):声明一个创建抽象产品对象的操作接口集合。通常每个抽象产品对应一个创建方法。
  2. ConcreteFactory (具体工厂):实现 AbstractFactory 接口,负责创建具体产品族中的产品对象。系统可以有多个具体工厂,每个具体工厂创建一个具体的产品族。
  3. AbstractProduct (抽象产品):为一类产品对象声明一个接口。系统中可以有多个不同的抽象产品,构成产品族。
  4. ConcreteProduct (具体产品):定义一个将被相应的具体工厂创建的产品对象。它实现了 AbstractProduct 接口。
  5. Client (客户端):仅使用 AbstractFactory 和 AbstractProduct 接口。客户端不关心具体是哪个工厂、哪个产品,它只知道它需要一个工厂来创建它需要的产品。
    在这里插入图片描述

4. 适用场景 (When to Use)

  • 一个系统要独立于它的产品的创建、组合和表示时。即,你希望客户端代码与具体产品的创建过程分离。
  • 一个系统要由多个产品系列中的一个来配置时。例如,系统需要支持多种“外观感觉”(Look and Feel)。
  • 当你要强调一系列相关的产品对象的设计以便进行联合使用时。
  • 当你提供一个产品类库,而只想显示它们的接口而不是实现时。

简单来说:

  • 需要创建的产品对象有复杂的关联关系(属于同一个产品族)
  • 系统需要支持不同系列(族)的产品,并且可以在运行时切换

5. 优缺点 (Pros and Cons)

优点:

  1. 分离接口和实现:客户端使用抽象接口,与具体的产品实现解耦。
  2. 易于交换产品系列:改变具体工厂即可改变整个产品系列,客户端代码无需修改。
  3. 有利于产品的一致性:当一个系列的产品对象被设计成一起工作时,抽象工厂模式能够保证客户端始终只使用同一个产品系列中的对象。

缺点:

  1. 难以扩展新的产品种类 (Product Kind):如果要在产品族中增加一个新的产品种类(例如,在UI主题工厂中增加创建 ScrollBar 的方法),那么所有的抽象工厂接口和具体工厂实现都需要修改,这违反了开闭原则。对于这种情况,工厂方法模式可能更合适(每个产品种类一个工厂方法)。
  2. 类的数量会显著增加:每增加一个产品族,就需要增加一套对应的具体产品类和具体工厂类。

6. 实现方式 (Implementations)

让我们通过一个跨平台UI组件的例子来看看抽象工厂模式的实现。假设我们需要为Windows和macOS创建风格一致的按钮和文本框。

抽象产品 (Button, TextBox)
// ui_elements.go
package ui// Button 按钮接口 (抽象产品A)
type Button interface {Render()OnClick()
}// TextBox 文本框接口 (抽象产品B)
type TextBox interface {Render()SetText(text string)GetText() string
}
// Button.java
package com.example.ui;// 按钮接口 (抽象产品A)
public interface Button {void render();void onClick();
}// TextBox.java
package com.example.ui;// 文本框接口 (抽象产品B)
public interface TextBox {void render();void setText(String text);String getText();
}
具体产品 (WindowsButton, WindowsTextBox, MacButton, MacTextBox)
// windows_elements.go
package uiimport "fmt"// WindowsButton Windows风格按钮 (具体产品A1)
type WindowsButton struct{}func (b *WindowsButton) Render()  { fmt.Println("Rendering a Windows style button.") }
func (b *WindowsButton) OnClick() { fmt.Println("Windows button clicked.") }// WindowsTextBox Windows风格文本框 (具体产品B1)
type WindowsTextBox struct{ text string }func (tb *WindowsTextBox) Render()        { fmt.Println("Rendering a Windows style text box.") }
func (tb *WindowsTextBox) SetText(text string) { tb.text = text }
func (tb *WindowsTextBox) GetText() string   { return tb.text }// mac_elements.go
package uiimport "fmt"// MacButton Mac风格按钮 (具体产品A2)
type MacButton struct{}func (b *MacButton) Render()  { fmt.Println("Rendering a macOS style button.") }
func (b *MacButton) OnClick() { fmt.Println("macOS button clicked.") }// MacTextBox Mac风格文本框 (具体产品B2)
type MacTextBox struct{ text string }func (tb *MacTextBox) Render()        { fmt.Println("Rendering a macOS style text box.") }
func (tb *MacTextBox) SetText(text string) { tb.text = text }
func (tb *MacTextBox) GetText() string   { return tb.text }
// WindowsButton.java
package com.example.ui.windows;import com.example.ui.Button;
import java.awt.FlowLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;// Windows风格按钮 (具体产品A1)
public class WindowsButton implements Button {@Overridepublic void render() {System.out.println("Rendering a Windows style button.");// 实际场景中可能会使用Swing/JavaFX等创建真实UI// JFrame frame = new JFrame("Windows Button");// JButton button = new JButton("Win Button");// button.addActionListener(e -> onClick());// frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// frame.setLayout(new FlowLayout());// frame.add(button);// frame.setSize(200, 100);// frame.setVisible(true);}@Overridepublic void onClick() {System.out.println("Windows button clicked.");// JOptionPane.showMessageDialog(null, "Windows Button Clicked!");}
}// WindowsTextBox.java
package com.example.ui.windows;import com.example.ui.TextBox;// Windows风格文本框 (具体产品B1)
public class WindowsTextBox implements TextBox {private String text = "";@Overridepublic void render() {System.out.println("Rendering a Windows style text box: [" + text + "]");}@Overridepublic void setText(String text) { this.text = text; }@Overridepublic String getText() { return this.text; }
}// MacButton.java
package com.example.ui.mac;import com.example.ui.Button;// Mac风格按钮 (具体产品A2)
public class MacButton implements Button {@Overridepublic void render() {System.out.println("Rendering a macOS style button.");}@Overridepublic void onClick() {System.out.println("macOS button clicked.");}
}// MacTextBox.java
package com.example.ui.mac;import com.example.ui.TextBox;// Mac风格文本框 (具体产品B2)
public class MacTextBox implements TextBox {private String text = "";@Overridepublic void render() {System.out.println("Rendering a macOS style text box: (" + text + ")");}@Overridepublic void setText(String text) { this.text = text; }@Overridepublic String getText() { return this.text; }
}
抽象工厂 (GUIFactory)
// gui_factory.go
package ui// GUIFactory 抽象UI工厂 (抽象工厂)
type GUIFactory interface {CreateButton() ButtonCreateTextBox() TextBox
}
// GUIFactory.java
package com.example.ui;// 抽象UI工厂 (抽象工厂)
public interface GUIFactory {Button createButton();TextBox createTextBox();
}
具体工厂 (WindowsFactory, MacFactory)
// windows_factory.go
package ui// WindowsFactory Windows UI工厂 (具体工厂1)
type WindowsFactory struct{}func (wf *WindowsFactory) CreateButton() Button {return &WindowsButton{}
}
func (wf *WindowsFactory) CreateTextBox() TextBox {return &WindowsTextBox{}
}// mac_factory.go
package ui// MacFactory Mac UI工厂 (具体工厂2)
type MacFactory struct{}func (mf *MacFactory) CreateButton() Button {return &MacButton{}
}
func (mf *MacFactory) CreateTextBox() TextBox {return &MacTextBox{}
}
// WindowsFactory.java
package com.example.ui.windows;import com.example.ui.Button;
import com.example.ui.GUIFactory;
import com.example.ui.TextBox;// Windows UI工厂 (具体工厂1)
public class WindowsFactory implements GUIFactory {@Overridepublic Button createButton() {System.out.println("WindowsFactory: Creating WindowsButton");return new WindowsButton();}@Overridepublic TextBox createTextBox() {System.out.println("WindowsFactory: Creating WindowsTextBox");return new WindowsTextBox();}
}// MacFactory.java
package com.example.ui.mac;import com.example.ui.Button;
import com.example.ui.GUIFactory;
import com.example.ui.TextBox;// Mac UI工厂 (具体工厂2)
public class MacFactory implements GUIFactory {@Overridepublic Button createButton() {System.out.println("MacFactory: Creating MacButton");return new MacButton();}@Overridepublic TextBox createTextBox() {System.out.println("MacFactory: Creating MacTextBox");return new MacTextBox();}
}
客户端使用 (Application)
// main.go (示例用法)
/*
package mainimport ("fmt""./ui" // 假设 ui 包在当前目录下"runtime"
)// Application 客户端,它不知道具体的工厂和产品类
type Application struct {factory GUIFactorybutton  ButtontextBox TextBox
}func NewApplication(factory ui.GUIFactory) *Application {app := &Application{factory: factory}app.button = factory.CreateButton()app.textBox = factory.CreateTextBox()return app
}func (app *Application) Run() {app.button.Render()app.button.OnClick()app.textBox.SetText("Hello Abstract Factory!")app.textBox.Render()fmt.Println("Text from box:", app.textBox.GetText())
}func main() {var factory ui.GUIFactory// 根据操作系统选择不同的工厂os := runtime.GOOSfmt.Println("Operating System:", os)if os == "windows" {factory = &ui.WindowsFactory{}} else if os == "darwin" { // darwin is macOSfactory = &ui.MacFactory{}} else {fmt.Println("Unsupported OS, defaulting to Windows style.")factory = &ui.WindowsFactory{} // 默认或提供一个通用工厂}app := NewApplication(factory)app.Run()
}
*/
// Application.java (客户端)
package com.example;import com.example.ui.Button;
import com.example.ui.GUIFactory;
import com.example.ui.TextBox;
import com.example.ui.mac.MacFactory;
import com.example.ui.windows.WindowsFactory;public class Application {private Button button;private TextBox textBox;public Application(GUIFactory factory) {System.out.println("Client: Configuring application with a UI factory.");button = factory.createButton();textBox = factory.createTextBox();}public void run() {System.out.println("\nClient: Running the application UI...");button.render();button.onClick();textBox.setText("Hello Abstract Factory!");textBox.render();System.out.println("Text from box: " + textBox.getText() + "\n");}// Main.java (示例用法)/*public static void main(String[] args) {GUIFactory factory;Application app;String osName = System.getProperty("os.name").toLowerCase();System.out.println("Operating System: " + osName);if (osName.contains("win")) {factory = new WindowsFactory();} else if (osName.contains("mac")) {factory = new MacFactory();} else {System.out.println("Unsupported OS, defaulting to Windows style.");factory = new WindowsFactory(); // Default factory}app = new Application(factory);app.run();// 假设我们现在想切换到Mac主题 (如果当前不是Mac)if (!osName.contains("mac")) {System.out.println("\n--- Switching to Mac Theme for demonstration ---");factory = new MacFactory();app = new Application(factory);app.run();}}*/
}

7. 与工厂方法模式的区别

抽象工厂模式和工厂方法模式是初学者容易混淆的两个模式。

  • 工厂方法模式 (Factory Method)

    • 关注点:创建单个产品对象
    • 结构:一个抽象工厂接口(通常只有一个创建方法 factoryMethod()),多个具体工厂实现它来创建不同的具体产品。
    • 目的:延迟产品的实例化到子类。
    • 解决问题:如何创建一个对象,但让子类决定具体创建哪个对象。
  • 抽象工厂模式 (Abstract Factory)

    • 关注点:创建一系列相关的产品对象(一个产品族)
    • 结构:一个抽象工厂接口(包含多个创建不同种类产品的抽象方法,如 createProductA(), createProductB()),多个具体工厂实现它来创建属于同一个产品族的不同具体产品。
    • 目的:提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们具体的类。
    • 解决问题:如何创建一组相互关联/依赖的对象,并保证它们之间是兼容的。

简单来说

  • 如果你只需要创建一种产品,但希望由子类决定具体创建哪种类型,用工厂方法
  • 如果你需要创建多种产品,这些产品需要配套使用(属于一个系列/族),并且希望客户端与具体产品解耦,用抽象工厂

实际上,抽象工厂模式的实现中,每个具体工厂内部的创建方法(如 createButton())通常可以使用工厂方法模式来实现,或者直接 new 具体产品。

8. 总结

抽象工厂模式是创建型模式中功能最强大但也相对复杂的模式之一。它通过提供一个抽象接口来创建一系列相关的产品对象(产品族),使得客户端代码可以独立于具体的产品实现。这对于需要支持多种产品系列(例如不同的UI主题、不同的数据库实现)并且希望在它们之间轻松切换的系统非常有用。

记住它的核心:创建产品家族,保证兼容性

相关文章:

  • C++23关联容器的异质擦除重载 (P2077R2)介绍
  • 物流项目第五期(运费计算实现、责任链设计模式运用)
  • 自动驾驶中的预测控制算法:用 Python 让无人车更智能
  • 第六章 Freertos智能小车循迹模块
  • 2025.05.21华为暑期实习机考真题解析第二题
  • jenkins数据备份
  • 改写视频生产流程!快手SketchVideo开源:通过线稿精准控制动态分镜的AI视频生成方案
  • Spring Boot AI 之 Chat Client API 使用大全
  • Spring Boot + +小程序, 快速开发零工市场小程序
  • 游戏引擎学习第303天:尝试分开对Y轴和Z轴进行排序
  • GPU加速Kubernetes集群助力音视频转码与AI工作负载扩展
  • 野火鲁班猫(arrch64架构debian)从零实现用MobileFaceNet算法进行实时人脸识别(四)安装RKNN Toolkit Lite2
  • DataGridView中拖放带有图片的Excel,实现数据批量导入
  • 【JAVA】比较器Comparator与自然排序(28)
  • 【Java】泛型在 Java 中是怎样实现的?
  • PostgreSQL 日常维护
  • VLA模型:自动驾驶与机器人行业的革命性跃迁,端到端智能如何重塑未来?
  • 【C++】移动语义与move()实用性教学
  • Docker网关冲突导致容器启动网络异常解决方案
  • 蓝桥杯3503 更小的数
  • 佛山市做网站的公司/网络推广发展
  • h5混搭php建设网站/山东seo优化
  • 找别人做网站的注意事项/企业品牌推广
  • 建筑材料市场信息价网/网络搜索引擎优化
  • 教做游戏的网站/百度一下主页官网
  • 凡科网站怎么做链接/免费男女打扑克的软件