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中,我们主要讨论两种工厂模式:
- 简单工厂模式:不属于标准的23种设计模式,但非常常用。它由一个“超级工厂”来决定创建哪一种产品类的实例。
- 工厂方法模式:定义一个用于创建对象的接口,但让子类决定实例化哪一个类。它将对象的创建延迟到子类中进行。
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.Open、http.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
========================================
