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

文章目录

    • 一、工厂模式概述
      • [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中,我们主要讨论两种工厂模式:

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

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.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

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
========================================
相关推荐
AI视觉网奇7 小时前
coco json 分类标注工具源代码
开发语言·python
勇敢牛牛_7 小时前
Rust真的适合写业务后端吗?
开发语言·后端·rust
要加油GW8 小时前
python使用vscode 需要配置全局的环境变量。
开发语言·vscode·python
B站计算机毕业设计之家8 小时前
python图像识别系统 AI多功能图像识别检测系统(11种识别功能)银行卡、植物、动物、通用票据、营业执照、身份证、车牌号、驾驶证、行驶证、车型、Logo✅
大数据·开发语言·人工智能·python·图像识别·1024程序员节·识别
ceclar1238 小时前
C++日期与时间
开发语言·c++
懒羊羊不懒@8 小时前
JavaSe—泛型
java·开发语言·人工智能·windows·设计模式·1024程序员节
Zhangzy@8 小时前
Rust Workspace 构建多项目体系
开发语言·前端·rust
麦麦鸡腿堡8 小时前
Java的三代日期类(Date,Calendar,LocalDateTime)
java·开发语言
一点七加一11 小时前
Harmony鸿蒙开发0基础入门到精通Day07--JavaScript篇
开发语言·javascript·ecmascript