文章目录
-
- 一、工厂模式概述
-
- [1.1 工厂模式介绍](#1.1 工厂模式介绍)
- [1.2 工厂模式的核心思想](#1.2 工厂模式的核心思想)
- [1.3 工厂模式的分类](#1.3 工厂模式的分类)
- [1.4 工厂模式对比](#1.4 工厂模式对比)
- 二、简单工厂模式
-
- [2.1 结构](#2.1 结构)
- [2.2 Go实现:形状工厂](#2.2 Go实现:形状工厂)
- 三、工厂方法模式
-
- [3.1 结构](#3.1 结构)
- [3.2 Go实现示例:日志工厂](#3.2 Go实现示例:日志工厂)
- 四、Go语言中的惯用写法(更简洁的工厂)
-
- [4.1 案例:数据库连接工厂](#4.1 案例:数据库连接工厂)
- 五、完整代码
-
- [5.1 项目结构](#5.1 项目结构)
- [5.2 简单工厂模式](#5.2 简单工厂模式)
- [5.3 工厂方法模式](#5.3 工厂方法模式)
- [5.4 Go惯用工厂函数](#5.4 Go惯用工厂函数)
- [5.5 主程序和完整执行结果](#5.5 主程序和完整执行结果)
- [5.6 最终完整执行结果](#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:定义产品接口和具体产品
go
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:创建简单工厂
go
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:客户端使用
go
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:定义产品接口和具体产品
go
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:创建工厂接口和具体工厂
go
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:客户端使用
go
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 案例:数据库连接工厂
go
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{}
}
客户端使用:
go
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
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
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
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 文件。
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
========================================