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

Go语言设计模式:工厂模式详解

文章目录

    • 一、工厂模式概述
      • 1.1 工厂模式介绍
      • 1.2 工厂模式的核心思想
      • 1.3 工厂模式的分类
      • 1.4 工厂模式对比
    • 二、简单工厂模式
      • 2.1 结构
      • 2.2 Go实现:形状工厂
    • 三、工厂方法模式
      • 3.1 结构
      • 3.2 Go实现示例:日志工厂
    • 四、Go语言中的惯用写法(更简洁的工厂)
      • 4.1 案例:数据库连接工厂
    • 五、完整代码
      • 5.1 项目结构
      • 5.2 简单工厂模式
      • 5.3 工厂方法模式
      • 5.4 Go惯用工厂函数
      • 5.5 主程序和完整执行结果
      • 5.6 最终完整执行结果

一、工厂模式概述

1.1 工厂模式介绍

工厂模式是创建型设计模式中最常用的一种。它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
简单来说,工厂模式就是将对象的创建过程封装起来,客户端只需告诉工厂“我想要什么”,而不用关心“它是如何被制造出来的”。想象一下:

  • 你想去喝杯咖啡,你只需要告诉咖啡店店员(工厂)“我要一杯拿铁”,你不需要关心咖啡豆怎么磨、牛奶怎么打泡。
  • 你需要一个数据库连接,你只需要告诉连接工厂(工厂)“给我一个MySQL连接”,你不需要关心TCP握手、认证等细节。

在大多数Go日常开发中,使用包级别的 NewXXX 函数作为工厂函数是最地道、最常见的选择。

1.2 工厂模式的核心思想

  • 解耦:将对象的创建与使用分离。客户端代码不依赖于具体的产品类,而是依赖于工厂接口。
  • 封装:将复杂的创建逻辑封装在工厂类中,对客户端隐藏实现细节。
  • 扩展性:当需要新增产品类型时,只需增加一个新的产品类和对应的工厂,而无需修改现有客户端代码,符合“开闭原则”。

1.3 工厂模式的分类

在Go中,我们主要讨论两种工厂模式:

  1. 简单工厂模式:不属于标准的23种设计模式,但非常常用。它由一个“超级工厂”来决定创建哪一种产品类的实例。
  2. 工厂方法模式:定义一个用于创建对象的接口,但让子类决定实例化哪一个类。它将对象的创建延迟到子类中进行。

1.4 工厂模式对比

特性简单工厂工厂方法Go惯用工厂函数
核心一个工厂类,根据参数创建产品一个工厂接口,每个产品对应一个工厂每个产品对应一个包级别的构造函数
优点结构简单,客户端调用方便符合开闭原则,扩展性好代码最简洁,非常符合Go的哲学
缺点违反开闭原则,扩展性差增加了系统类的数量没有统一的工厂接口,需要客户端了解具体函数
适用场景产品种类少,且未来基本不变产品种类多,且需要频繁扩展Go中最推荐的通用方式

二、简单工厂模式

简单工厂模式的核心是一个工厂类,它通常包含一个方法,该方法根据传入的参数来创建并返回不同的产品实例。

2.1 结构

  • Product (产品接口):定义所有产品共有的接口。
  • ConcreteProduct (具体产品):实现产品接口的具体类。
  • Factory (工厂):提供一个静态方法(在Go中通常是包级别的函数),根据参数创建具体产品。

2.2 Go实现:形状工厂

假设我们需要一个工厂来创建不同形状(圆形、方形)的对象。

步骤1:定义产品接口和具体产品

package shapes
// Shape 产品接口
type Shape interface {Draw() string
}
// Circle 具体产品1
type Circle struct{}
func (c *Circle) Draw() string {return "绘制一个圆形"
}
// Square 具体产品2
type Square struct{}
func (s *Square) Draw() string {return "绘制一个方形"
}

步骤2:创建简单工厂

package shapes
// ShapeFactory 简单工厂
type ShapeFactory struct{}
// NewShapeFactory 创建工厂实例
func NewShapeFactory() *ShapeFactory {return &ShapeFactory{}
}
// CreateShape 根据形状类型创建具体的产品
func (f *ShapeFactory) CreateShape(shapeType string) Shape {switch shapeType {case "circle":return &Circle{}case "square":return &Square{}default:// 可以返回nil或者一个默认的错误对象return nil}
}

步骤3:客户端使用

package main
import ("fmt""your_module_path/shapes" // 替换为你的模块路径
)
func main() {factory := shapes.NewShapeFactory()// 创建圆形circle := factory.CreateShape("circle")if circle != nil {fmt.Println(circle.Draw())}// 创建方形square := factory.CreateShape("square")if square != nil {fmt.Println(square.Draw())}// 尝试创建不存在的形状unknown := factory.CreateShape("triangle")if unknown == nil {fmt.Println("不支持的形状类型")}
}

优点:实现简单,客户端只需知道产品的类型字符串即可。
缺点:违反了“开闭原则”。当需要新增产品时,必须修改工厂的 CreateShape 方法(增加一个 case),这会带来风险。

三、工厂方法模式

为了解决简单工厂的缺点,工厂方法模式应运而生。它为每一种产品都提供一个对应的工厂。

3.1 结构

  • Product (产品接口):同上。
  • ConcreteProduct (具体产品):同上。
  • Creator (工厂接口):声明一个工厂方法,该方法返回一个产品对象。
  • ConcreteCreator (具体工厂):实现工厂方法,返回一个具体的产品实例。

3.2 Go实现示例:日志工厂

假设我们需要一个日志工厂,可以创建不同类型的日志记录器(文件日志、控制台日志)。
步骤1:定义产品接口和具体产品

package logger
// Logger 产品接口
type Logger interface {Log(message string)
}
// FileLogger 具体产品1
type FileLogger struct{}
func (f *FileLogger) Log(message string) {fmt.Printf("[文件日志] %s\n", message)
}
// ConsoleLogger 具体产品2
type ConsoleLogger struct{}
func (c *ConsoleLogger) Log(message string) {fmt.Printf("[控制台日志] %s\n", message)
}

步骤2:创建工厂接口和具体工厂

package logger
import "fmt"
// LoggerFactory 工厂接口
type LoggerFactory interface {CreateLogger() Logger
}
// FileLoggerFactory 具体工厂1
type FileLoggerFactory struct{}
func (f *FileLoggerFactory) CreateLogger() Logger {fmt.Println("创建文件日志记录器...")return &FileLogger{}
}
// ConsoleLoggerFactory 具体工厂2
type ConsoleLoggerFactory struct{}
func (c *ConsoleLoggerFactory) CreateLogger() Logger {fmt.Println("创建控制台日志记录器...")return &ConsoleLogger{}
}

步骤3:客户端使用

package main
import ("fmt""your_module_path/logger" // 替换为你的模块路径
)
func writeLog(factory logger.LoggerFactory) {// 客户端只关心工厂接口,不关心具体是哪个工厂logger := factory.CreateLogger()logger.Log("这是一条重要的日志信息")
}
func main() {fmt.Println("--- 使用文件日志工厂 ---")fileFactory := &logger.FileLoggerFactory{}writeLog(fileFactory)fmt.Println("\n--- 使用控制台日志工厂 ---")consoleFactory := &logger.ConsoleLoggerFactory{}writeLog(consoleFactory)
}

优点

  • 符合“开闭原则”。新增产品时,只需新增一个产品类和一个对应的工厂类,无需修改现有代码。
  • 代码结构更清晰,职责分离更明确。
    缺点
  • 类的数量会成对增加,在一定程度上增加了系统的复杂性。

四、Go语言中的惯用写法(更简洁的工厂)

在Go中,我们并不总是需要显式地定义一个工厂接口。更常见的做法是:将工厂方法定义在包中,作为包级别的API。这种方式结合了简单工厂的简洁性和工厂方法的扩展性。

4.1 案例:数据库连接工厂

package db
// DB 产品接口
type DB interface {Query(sql string)
}
// MySQLDB 具体产品
type MySQLDB struct{}
func (m *MySQLDB) Query(sql string) {fmt.Printf("MySQL 执行查询: %s\n", sql)
}
// PostgreSQLDB 具体产品
type PostgreSQLDB struct{}
func (p *PostgreSQLDB) Query(sql string) {fmt.Printf("PostgreSQL 执行查询: %s\n", sql)
}
// 工厂函数(Go惯用法)
func NewMySQLDB() *MySQLDB {fmt.Println("初始化MySQL连接...")return &MySQLDB{}
}
func NewPostgreSQLDB() *PostgreSQLDB {fmt.Println("初始化PostgreSQL连接...")return &PostgreSQLDB{}
}

客户端使用:

package main
import ("fmt""your_module_path/db"
)
func main() {fmt.Println("--- 创建MySQL数据库实例 ---")mysqlDB := db.NewMySQLDB()mysqlDB.Query("SELECT * FROM users")fmt.Println("\n--- 创建PostgreSQL数据库实例 ---")postgresDB := db.NewPostgreSQLDB()postgresDB.Query("SELECT * FROM products")
}

这种写法非常直观,是Go标准库和第三方库中最常见的工厂模式实现形式。例如 os.Openhttp.NewServeMux 等都可以看作是工厂函数。

五、完整代码

上面三种工厂模式案例的完整可运行代码如下:

5.1 项目结构

factory-demo/
├── go.mod
├── main.go
├── simple_factory/
│   └── shape.go
├── factory_method/
│   └── logger.go
└── go_idiomatic_factory/└── db.go

5.2 简单工厂模式

代码位置:simple_factory/shape.go

package simple_factory
import "fmt"
// Shape 产品接口
type Shape interface {Draw() string
}
// Circle 具体产品1
type Circle struct{}
func (c *Circle) Draw() string {return "绘制一个圆形"
}
// Square 具体产品2
type Square struct{}
func (s *Square) Draw() string {return "绘制一个方形"
}
// ShapeFactory 简单工厂
type ShapeFactory struct{}
// NewShapeFactory 创建工厂实例
func NewShapeFactory() *ShapeFactory {return &ShapeFactory{}
}
// CreateShape 根据形状类型创建具体的产品
func (f *ShapeFactory) CreateShape(shapeType string) Shape {switch shapeType {case "circle":return &Circle{}case "square":return &Square{}default:// 可以返回nil或者一个默认的错误对象fmt.Printf("错误:不支持的形状类型 '%s'\n", shapeType)return nil}
}

执行结果:在 main.go 中调用简单工厂的代码(见下文完整 main.go),运行后会得到以下输出:

===== 简单工厂模式 =====
绘制一个圆形
绘制一个方形
错误:不支持的形状类型 'triangle'

5.3 工厂方法模式

代码位置:factory_method/logger.go

package factory_method
import "fmt"
// Logger 产品接口
type Logger interface {Log(message string)
}
// FileLogger 具体产品1
type FileLogger struct{}
func (f *FileLogger) Log(message string) {fmt.Printf("[文件日志] %s\n", message)
}
// ConsoleLogger 具体产品2
type ConsoleLogger struct{}
func (c *ConsoleLogger) Log(message string) {fmt.Printf("[控制台日志] %s\n", message)
}
// LoggerFactory 工厂接口
type LoggerFactory interface {CreateLogger() Logger
}
// FileLoggerFactory 具体工厂1
type FileLoggerFactory struct{}
func (f *FileLoggerFactory) CreateLogger() Logger {fmt.Println("创建文件日志记录器...")return &FileLogger{}
}
// ConsoleLoggerFactory 具体工厂2
type ConsoleLoggerFactory struct{}
func (c *ConsoleLoggerFactory) CreateLogger() Logger {fmt.Println("创建控制台日志记录器...")return &ConsoleLogger{}
}

执行结果:在 main.go 中调用工厂方法模式的代码,运行后会得到以下输出:

===== 工厂方法模式 =====
--- 使用文件日志工厂 ---
创建文件日志记录器...
[文件日志] 这是一条重要的日志信息
--- 使用控制台日志工厂 ---
创建控制台日志记录器...
[控制台日志] 这是一条重要的日志信息

5.4 Go惯用工厂函数

代码位置:go_idiomatic_factory/db.go

package go_idiomatic_factory
import "fmt"
// DB 产品接口
type DB interface {Query(sql string)
}
// MySQLDB 具体产品
type MySQLDB struct{}
func (m *MySQLDB) Query(sql string) {fmt.Printf("MySQL 执行查询: %s\n", sql)
}
// PostgreSQLDB 具体产品
type PostgreSQLDB struct{}
func (p *PostgreSQLDB) Query(sql string) {fmt.Printf("PostgreSQL 执行查询: %s\n", sql)
}
// NewMySQLDB MySQL的工厂函数(Go惯用法)
func NewMySQLDB() *MySQLDB {fmt.Println("初始化MySQL连接...")return &MySQLDB{}
}
// NewPostgreSQLDB PostgreSQL的工厂函数(Go惯用法)
func NewPostgreSQLDB() *PostgreSQLDB {fmt.Println("初始化PostgreSQL连接...")return &PostgreSQLDB{}
}

执行结果:在 main.go 中调用Go惯用工厂函数的代码,运行后会得到以下输出:

===== Go惯用工厂函数 =====
--- 创建MySQL数据库实例 ---
初始化MySQL连接...
MySQL 执行查询: SELECT * FROM users
--- 创建PostgreSQL数据库实例 ---
初始化PostgreSQL连接...
PostgreSQL 执行查询: SELECT * FROM products

5.5 主程序和完整执行结果

这是整合了所有模式调用的 main.go 文件。

package main
import ("factory-demo/factory_method""factory-demo/go_idiomatic_factory""factory-demo/simple_factory""fmt"
)
func main() {fmt.Println("========================================")fmt.Println("===== 1. 简单工厂模式 =====")simpleFactoryDemo()fmt.Println("========================================")fmt.Println("\n===== 2. 工厂方法模式 =====")factoryMethodDemo()fmt.Println("========================================")fmt.Println("\n===== 3. Go惯用工厂函数 =====")goIdiomaticFactoryDemo()fmt.Println("========================================")
}
// 简单工厂模式演示
func simpleFactoryDemo() {factory := simple_factory.NewShapeFactory()// 创建圆形circle := factory.CreateShape("circle")if circle != nil {fmt.Println(circle.Draw())}// 创建方形square := factory.CreateShape("square")if square != nil {fmt.Println(square.Draw())}// 尝试创建不存在的形状unknown := factory.CreateShape("triangle")if unknown == nil {// 错误信息已在工厂内部打印}
}
// 工厂方法模式演示
func factoryMethodDemo() {writeLog := func(factory factory_method.LoggerFactory) {// 客户端只关心工厂接口,不关心具体是哪个工厂logger := factory.CreateLogger()logger.Log("这是一条重要的日志信息")}fmt.Println("--- 使用文件日志工厂 ---")fileFactory := &factory_method.FileLoggerFactory{}writeLog(fileFactory)fmt.Println("\n--- 使用控制台日志工厂 ---")consoleFactory := &factory_method.ConsoleLoggerFactory{}writeLog(consoleFactory)
}
// Go惯用工厂函数演示
func goIdiomaticFactoryDemo() {fmt.Println("--- 创建MySQL数据库实例 ---")mysqlDB := go_idiomatic_factory.NewMySQLDB()mysqlDB.Query("SELECT * FROM users")fmt.Println("\n--- 创建PostgreSQL数据库实例 ---")postgresDB := go_idiomatic_factory.NewPostgreSQLDB()postgresDB.Query("SELECT * FROM products")
}

5.6 最终完整执行结果

将以上所有文件按结构放置后,在 factory-demo 目录下运行 go run .,将得到如下完整的输出:

========================================
===== 1. 简单工厂模式 =====
绘制一个圆形
绘制一个方形
错误:不支持的形状类型 'triangle'
========================================
===== 2. 工厂方法模式 =====
--- 使用文件日志工厂 ---
创建文件日志记录器...
[文件日志] 这是一条重要的日志信息
--- 使用控制台日志工厂 ---
创建控制台日志记录器...
[控制台日志] 这是一条重要的日志信息
========================================
===== 3. Go惯用工厂函数 =====
--- 创建MySQL数据库实例 ---
初始化MySQL连接...
MySQL 执行查询: SELECT * FROM users
--- 创建PostgreSQL数据库实例 ---
初始化PostgreSQL连接...
PostgreSQL 执行查询: SELECT * FROM products
========================================
http://www.dtcms.com/a/557276.html

相关文章:

  • Docker 部署 openEuler 教程及常见问题解决
  • 厦门专业做网站 厦门做网站的公司 厦门做服饰网站网站开发程序员需要会的技能
  • W55MH32三模自由控:小程序按键网页随选
  • 物联网入侵检测技术综合综述报告
  • 大模型-Qwen-Agent框架:系列Agent功能介绍 (2)
  • 网站 设计理念淄博网站建设优化运营熊掌号
  • R 包的制作
  • 【矩阵分析与应用】【第5章 梯度分析与最优化】【5.2.2 矩阵迹的微分计算示例d(tr(U))=tr(dU)证明】
  • 岳阳网站设计公司网站开发意义
  • MySQL的CONCAT函数介绍
  • 潜山云建站网站建设wordpress获取当前用户id
  • makefile 函数全解
  • day01_牛客_数字统计_C++
  • Redis RDB 持久化机制深入理解:Copy-On-Write 与数据一致性保障
  • 做哪方面的网站阳泉哪里做网站
  • 电商网站改版方案有哪些免费的ppt模板下载网站
  • LeetCode 3217.从链表中移除在数组中存在的节点:哈希表(一次遍历)
  • LeetCode - 寻找两个正序数组的中位数
  • 上海网站设计公司 静安沙井建网站
  • VMware17完成克隆ubuntu20.04时IP地址相同的问题
  • 【问题排查】hadoop-shaded-guava依赖问题
  • 百度地图网页版在线使用搜索引擎优化搜索优化
  • 网站优化排名兰州网站建设尚美
  • leetcode 3217 从链表中移除在数组中存在的节点
  • C++音视频就业路线
  • 46-基于STM32的智能宠物屋设计与实现
  • blender实现手柄控制VR视角
  • 八股训练营第 2 天 | HTTP请求报文和响应报文是怎样的,有哪些常见的字段?HTTP有哪些请求方式?GET请求和POST请求的区别?
  • 【LUT技术专题】SVDLUT: 基于SVD优化的3DLUT
  • 阿里云企业邮箱怎么申请宿迁网站建设SEO优化营销