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

第二部分:创建型模式 - 抽象工厂模式 (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主题、不同的数据库实现)并且希望在它们之间轻松切换的系统非常有用。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/84061.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【通用智能体】Serper API 详解:搜索引擎数据获取的核心工具

Serper API 详解:搜索引擎数据获取的核心工具 一、Serper API 的定义与核心功能二、技术架构与核心优势2.1 技术实现原理2.2 对比传统方案的突破性优势 三、典型应用场景与代码示例3.1 SEO 监控系统3.2 竞品广告分析 四、使用成本与配额策略五、开发者注意事项六、替…

Flask-SQLAlchemy核心概念:模型类与数据库表、类属性与表字段、外键与关系映射

前置阅读,关于Flask-SQLAlchemy支持哪些数据库及基本配置,链接:Flask-SQLAlchemy_数据库配置 摘要 本文以一段典型的 SQLAlchemy 代码示例为引入,阐述以下核心概念: 模型类(Model Class) ↔ 数…

野火鲁班猫(arrch64架构debian)从零实现用MobileFaceNet算法进行实时人脸识别(四)安装RKNN Toolkit2

RKNN Toolkit2是用来将onnx模型转成rknn专用模型,并可通过RKNN Toolkit Lite2或者RKNPU调用NPU进行加速计算的工具。 一开始我安装很多次都无法成功安装。后来跟售后技术对接,必须是PC平台的Linux环境才可以。我的电脑是windows,所以我需要用…

基于深度学习的工件检测系统设计与实现

在工业自动化领域,工件检测一直是提高生产效率和产品质量的关键环节。传统的人工检测方法不仅效率低下,而且容易受到主观因素的影响,导致误判率较高。随着深度学习技术的飞速发展,基于图像识别的自动检测系统逐渐成为研究热点。今…

CyberSecAsia专访CertiK首席安全官:区块链行业亟需“安全优先”开发范式

近日,权威网络安全媒体CyberSecAsia发布了对CertiK首席安全官Wang Tielei博士的专访,双方围绕企业在进军区块链领域时所面临的关键安全风险与防御策略展开深入探讨。 Wang博士在采访中指出,跨链桥攻击、智能合约漏洞以及私钥管理不当&#x…

Google C++ Style Guide 谷歌 C++编码风格指南,深入理解华为与谷歌的编程规范——C和C++实践指南

Google C 编程风格指南 Release Apr 07, 2017 0. ᡿享 ⡾ᵢ 4.45 ৕֒㘻 Benjy Weinberger, Craig Silverstein, Gregory Eitzmann, Mark Mentovai, Tashana Landray 㘱䈇 YuleFox, Yang.Y, acgtyrant, lilinsanity 亯ⴤѱ享 • Google Style Guide • Google 开源…

当科技邂逅浪漫:在Codigger的世界里,遇见“爱”

520,一个充满爱意的日子,人们用各种方式表达对彼此的深情。而在科技的世界里,我们也正经历着一场特别的邂逅——Codigger,一个分布式操作系统的诞生,正在以它独特的方式,重新定义我们与技术的关系。 Codigg…

嵌入式学习笔记 - Void类型的指针

void指针的基本概念和特性 void指针是一种特殊的指针类型,称为“无类型指针”或“通用指针”。它的主要特点是: ‌通用性‌:void指针可以指向任何类型的数据,这使得它在处理不确定数据类型时非常有用。 ‌灵活性‌:由…

【综述】视频目标分割VOS

相关连接 更新中....... 1、Associating Objects with Transformers for Video Object Segmentation:论文详解、AOT源码解析 2、Rethinking Space-Time Networks with Improved Memory Coverage for Efficient Video Object Segmentation 3、Recurrent Dynamic Embe…

001 嵌入式软件开发工程师实习篇面试——首战总结

2025年5月17日人生中第一次面试 紧张是藏不住的。但是不应该的。 目录 0.准备一份合适的自我介绍 1.结构体内存对齐问题 2.变量在内存中的存储模式 3.嵌入式中程序框架有哪些 4.程序代码设计要遵循什原则 5.版本号书写 6.单片机最小系统板有哪些组成 必须: 非必须:…

SIL2/PLd 认证 Inxpect毫米波安全雷达:3D 扫描 + 微小运动检测守护工业安全

Inxpect 成立于意大利,专注工业安全技术。自成立起,便致力于借助先进雷达技术提升工业自动化安全标准,解决传统安全设备在复杂环境中的局限,推出获 SIL2/PLd 和 UL 认证的安全雷达产品。 Inxpect 的雷达传感器技术优势明显。相较于…

Python数据可视化再探——Matplotlib模块 之一

目录 第一章 Matplotlib 模块教学内容​——基础图形绘制 一、Pyplot 子库介绍​ 1. 功能概述​ 2. 常用函数​ 二、绘制基本图形​ 1. 柱状图​ 2. 条形图​ 3. 折线图​ 4. 散点图​ 5. 面积图​ 6. 饼状图​ 7. 圆环图​ ​编辑 三、绘图知识点详解​ 1. 绘图…

智慧在线判题OJ系统项目总体,包含功能开发思路,内部中间件,已经部分知识点

目录 回顾一下xml文件怎么写 哪个地方使用了哪个技术 MyBatis-Plus-oj的表结构设计, 管理员登录功能 Swagger Apifox​编辑 BCrypt 日志框架引入(slf4jlogback) nacos Swagger无法被所有微服务获取到修改的原因 身份认证三种方式: JWT(Json Web Json,一…

使用Spring Boot和Spring Security构建安全的RESTful API

使用Spring Boot和Spring Security构建安全的RESTful API 引言 在现代Web应用开发中,安全性是至关重要的。Spring Boot和Spring Security是Java生态中广泛使用的框架,它们提供了强大的工具来保护RESTful API。本文将介绍如何结合Spring Boot和Spring S…

虚幻引擎5-Unreal Engine笔记之`GameMode`、`关卡(Level)` 和 `关卡蓝图(Level Blueprint)`的关系

虚幻引擎5-Unreal Engine笔记之GameMode、关卡(Level) 和 关卡蓝图(Level Blueprint)的关系 code review! 参考笔记: 1.虚幻引擎5-Unreal Engine笔记之GameMode、关卡(Level) 和 关卡蓝图&…

Java+Selenium+快代理实现高效爬虫

目录 一、前言二、Selenium简介三、环境准备四、代码实现4.1 创建WebDriver工厂类4.2 创建爬虫主类4.3 配置代理的注意事项 六、总结与展望 一、前言 在Web爬虫技术中,Selenium作为一款强大的浏览器自动化工具,能够模拟真实用户操作,有效应对…

SpringBoot配置文件的合并

需求:想分类将mysql数据库的配置放在一个文件,redis的配置放在另外一个文件 就不去引入mysql和redis了,看能否得到值就行了 测试结果 model的包放错了 应该移动到demo里 能否用yml或者yaml呢 这里注意yml的写法 测试结果也是可以的 注意如果主配置文件是yml或者yaml的话

深入理解 BFC:网页布局的关键机制

在前端开发的世界里,网页布局是一项至关重要的任务。而在众多布局相关的概念中,BFC(Block Formatting Context,块级格式化上下文)扮演着极为关键的角色。今天,就让我们深入剖析 BFC 的方方面面。 一、BFC …

04-Web后端基础(基础知识)

而像HTML、CSS、JS 以及图片、音频、视频等这些资源,我们都称为静态资源。 所谓静态资源,就是指在服务器上存储的不会改变的数据,通常不会根据用户的请求而变化。 那与静态资源对应的还有一类资源,就是动态资源。那所谓动态资源&…

Vue3 Element Plus el-table-column Sortable 排序失效

问题描述&#xff1a; vue3中 element plus 中 el-table 的 el-table-column使用了插槽后&#xff0c;为什么sortable不起效果&#xff0c;不能点击排序 <el-table-columnlabel"记账日期"width"110"fixed"left"header-align"left"…