设计模式(Design Pattern)是软件工程中针对常见问题的通用解决方案。

它们不是具体的代码,而是经过验证的最佳实践,帮助开发者设计出灵活、可维护和可扩展的软件系统

使用设计模式的好处

  1. 面试
  2. 提高代码复用性,写出高质量代码
  3. 前人总结的最佳实践,在合适的地方用合适的设计模式,可以事半功倍

创建型模式

在 Go 语言中,创建型模式(Creational Patterns)是一类用于处理对象创建的设计模式。它们的主要目标是提供一种灵活的方式来创建对象,同时隐藏对象创建的具体细节,从而降低代码的耦合度,并提高代码的可复用性和可维护性。

比如http.NewRequest()bytes.NewReader()md5.New()

创建型模式的核心思想是将对象的创建与使用分离,使得系统不依赖于具体的对象创建方式,而是依赖于抽象。

单例模式 Singleton

确保一个类只有一个实例,并提供一个全局访问点。

适用场景:

  • 配置管理、日志记录、数据库连接池等需要全局唯一实例的场景。

之前在项目里面,我们之前一直在用的global.DB,global.Config其实和这个差不多,但是它不是单例模式,因为是直接使用的对应的对象,而不是通过函数返回的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
)

type DB struct {
Database string
}

var db *DB

func GetDB() *DB {
db = &DB{
Database: "fengfeng",
}
return db
}

func main() {
d := db
fmt.Printf("%p, %v\n", d, d.Database)
}

之前是直接使用全局变量,但是这个变量是存在为nil的情况的,这种情况下再使用它就是为出现空指针的情况

那么可以使用一个函数,在里面判断一下,如果这个对象是nil,就去初始化对象,后续如果有的话,就直接返回之前的那个创建好的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"fmt"
"sync"
)

type DB struct {
Database string
}

var db *DB
var once sync.Once

func GetDB() *DB {
once.Do(func() {
db = &DB{
Database: "fengfeng",
}
})
return db
}

func main() {
d := GetDB()
fmt.Printf("%p, %v\n", d, d.Database)
d = GetDB()
fmt.Printf("%p, %v\n", d, d.Database)
}

简单工厂模式 Simple Factory Pattern

它们的目标都是将对象的创建与使用分离,从而降低代码的耦合度

但是具体的工厂还是有区别

模式特点适用场景
简单工厂一个工厂类负责创建所有产品,通过条件判断决定创建哪种产品。产品种类较少,创建逻辑简单。
工厂方法模式每个产品对应一个工厂类,符合开闭原则。产品种类较多,创建逻辑复杂。
抽象工厂模式每个工厂类可以创建一组相关产品,强调产品族的概念。需要创建一组相关对象的场景。

简单工厂并不是一个正式的设计模式,而是一种编程习惯。它通过一个工厂类来封装对象的创建逻辑,客户端只需要传递参数给工厂类,由工厂类决定创建哪种对象。

特点

  • 只有一个工厂类,负责创建所有产品。
  • 通过条件判断(如 switch 或 if-else)来决定创建哪种产品。

适用场景

  • 产品种类较少,且创建逻辑简单的场景。

开闭原则:当需求发生变化时,可以通过增加新的代码来扩展系统的功能,而不是修改现有的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main

import "fmt"

type Product interface {
Use()
}

type ProductA struct{}

func (p *ProductA) Use() {
fmt.Println("Using Product A")
}

type ProductB struct{}

func (p *ProductB) Use() {
fmt.Println("Using Product B")
}

func CreateProduct(productType string) Product {
switch productType {
case "A":
return &ProductA{}
case "B":
return &ProductB{}
default:
return nil
}
}

func main() {
productA := CreateProduct("A")
productA.Use()

productB := CreateProduct("B")
productB.Use()
}

优点

  • 简单易用,适合小型项目。

缺点

  • 不符合开闭原则(OCP),新增产品时需要修改工厂类。

工厂方法模式 Factory Method

之前的简单工厂模式,一个工厂就负责了好几个产品的生产

工厂方法模式则是定义了一个创建对象的接口,但将具体的创建逻辑延迟到子类中。每个子类负责创建一种具体的产品。

特点

  • 每个产品对应一个工厂类。
  • 符合开闭原则,新增产品时只需增加新的工厂类,无需修改现有代码。

适用场景

  • 产品种类较多,且创建逻辑复杂的场景。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package main

import "fmt"

type Database interface {
Connect() string
}

// MySQL 操作
type MySQL struct{}

func (m *MySQL) Connect() string {
return "Connected to MySQL"
}

// PostgreSQL 操作
type PostgreSQL struct{}

func (p *PostgreSQL) Connect() string {
return "Connected to PostgreSQL"
}

// DatabaseFactory 工厂接口
type DatabaseFactory interface {
CreateDatabase() Database
}

// MySQLFactory MySQL 工厂
type MySQLFactory struct{}

func (m *MySQLFactory) CreateDatabase() Database {
return &MySQL{}
}

// PostgreSQL 工厂
type PostgreSQLFactory struct{}

func (p *PostgreSQLFactory) CreateDatabase() Database {
return &PostgreSQL{}
}

// UseDatabase 使用数据库
func UseDatabase(factory DatabaseFactory) {
db := factory.CreateDatabase()
fmt.Println(db.Connect())
}
func main() {
// 使用 MySQL
mysqlFactory := &MySQLFactory{}
UseDatabase(mysqlFactory)

// 使用 PostgreSQL
postgresFactory := &PostgreSQLFactory{}
UseDatabase(postgresFactory)
}

优点

  • 符合开闭原则,扩展性强。
  • 每个工厂只负责一种产品的创建,职责单一。

缺点

  • 类的数量会增加,系统复杂度提高。

抽象工厂模式 Abstract Factory

抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。它适用于需要创建一组相关产品的场景。

特点

  • 每个工厂类可以创建多个相关产品。
  • 强调产品族的概念,例如 GUI 库中的不同风格组件(Windows 风格、Mac 风格)。

适用场景

  • 需要创建一组相关对象的场景。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package main

import "fmt"

// DBConnection 抽象产品:数据库连接接口
type DBConnection interface {
Connect() string
}

// DBCommand 抽象产品:数据库命令接口
type DBCommand interface {
Execute(query string) string
}

// MySQLConnection 具体产品:MySQL 连接
type MySQLConnection struct{}

func (m *MySQLConnection) Connect() string {
return "Connected to MySQL"
}

// MySQLCommand 具体产品:MySQL 命令
type MySQLCommand struct{}

func (m *MySQLCommand) Execute(query string) string {
return fmt.Sprintf("MySQL executing query: %s", query)
}

// PostgreSQLConnection 具体产品:PostgreSQL 连接
type PostgreSQLConnection struct{}

func (p *PostgreSQLConnection) Connect() string {
return "Connected to PostgreSQL"
}

// PostgreSQLCommand 具体产品:PostgreSQL 命令
type PostgreSQLCommand struct{}

func (p *PostgreSQLCommand) Execute(query string) string {
return fmt.Sprintf("PostgreSQL executing query: %s", query)
}

// DBFactory 抽象工厂接口
type DBFactory interface {
CreateConnection() DBConnection
CreateCommand() DBCommand
}

// MySQLFactory 具体工厂:MySQL 工厂
type MySQLFactory struct{}

func (m *MySQLFactory) CreateConnection() DBConnection {
return &MySQLConnection{}
}

func (m *MySQLFactory) CreateCommand() DBCommand {
return &MySQLCommand{}
}

// PostgreSQLFactory 具体工厂:PostgreSQL 工厂
type PostgreSQLFactory struct{}

func (p *PostgreSQLFactory) CreateConnection() DBConnection {
return &PostgreSQLConnection{}
}

func (p *PostgreSQLFactory) CreateCommand() DBCommand {
return &PostgreSQLCommand{}
}

func UseDatabase(factory DBFactory) {
connection := factory.CreateConnection()
command := factory.CreateCommand()

fmt.Println(connection.Connect())
fmt.Println(command.Execute("SELECT * FROM users"))
}

func main() {
// 使用 MySQL
mysqlFactory := &MySQLFactory{}
UseDatabase(mysqlFactory)

// 使用 PostgreSQL
postgresFactory := &PostgreSQLFactory{}
UseDatabase(postgresFactory)
}

优点

  • 可以创建一组相关对象,保证对象之间的兼容性。
  • 符合开闭原则,扩展性强。

缺点

  • 类的数量会增加,系统复杂度提高。
  • 新增产品族或产品等级结构时,需要修改抽象工厂接口及其所有实现类。

抽象工厂模式和工厂方法模式的区别

特性工厂方法模式抽象工厂模式
产品数量一个工厂方法只创建一个产品。一个抽象工厂创建多个相关产品(产品族)。
产品关系产品之间没有直接关系。产品之间是相关的(属于同一个产品族)。
扩展性扩展时需要新增具体工厂类。扩展时需要新增具体工厂类和产品族。
实现方式通过继承实现。通过组合实现。
适用场景单一产品的创建。多个相关产品的创建。
  • 工厂方法模式 更简单,适用于单一产品的创建。
  • 抽象工厂模式 更强大,适用于创建多个相关产品,但实现也更复杂。

建造者模式 Builder

它用于分步构建复杂对象。建造者模式的核心思想是将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。

建造者模式特别适用于以下场景:

  • 对象的构建过程非常复杂,包含多个步骤。
  • 对象的构建过程需要支持不同的配置或表示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package main

import "fmt"

// 产品:House
type House struct {
Walls string
Roof string
Windows string
Doors string
}

func (h *House) Show() {
fmt.Printf("House with %s walls, %s roof, %s windows, and %s doors\n",
h.Walls, h.Roof, h.Windows, h.Doors)
}

// 建造者接口
type HouseBuilder interface {
BuildWalls()
BuildRoof()
BuildWindows()
BuildDoors()
GetHouse() *House
}

// 具体建造者:ConcreteHouseBuilder
type ConcreteHouseBuilder struct {
house *House
}

func NewConcreteHouseBuilder() *ConcreteHouseBuilder {
return &ConcreteHouseBuilder{house: &House{}}
}

func (b *ConcreteHouseBuilder) BuildWalls() {
b.house.Walls = "concrete"
}

func (b *ConcreteHouseBuilder) BuildRoof() {
b.house.Roof = "tile"
}

func (b *ConcreteHouseBuilder) BuildWindows() {
b.house.Windows = "glass"
}

func (b *ConcreteHouseBuilder) BuildDoors() {
b.house.Doors = "wooden"
}

func (b *ConcreteHouseBuilder) GetHouse() *House {
return b.house
}

// 指挥者
type Director struct {
builder HouseBuilder
}

func NewDirector(builder HouseBuilder) *Director {
return &Director{builder: builder}
}

func (d *Director) Construct() {
d.builder.BuildWalls()
d.builder.BuildRoof()
d.builder.BuildWindows()
d.builder.BuildDoors()
}

// 客户端代码
func main() {
// 创建具体建造者
builder := NewConcreteHouseBuilder()

// 创建指挥者
director := NewDirector(builder)

// 指挥者构建产品
director.Construct()

// 获取最终产品
house := builder.GetHouse()
house.Show()
}

原型模式 Prototype

它通过复制现有对象来创建新对象,而不是通过新建类的方式。原型模式的核心思想是利用对象的克隆能力,避免重复初始化,特别适用于创建成本较高的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package main

import "fmt"

// Prototype 原型接口
type Prototype interface {
Clone() Prototype
}

// ConcretePrototype 具体原型
type ConcretePrototype struct {
Name string
Age int
}

func (p *ConcretePrototype) Clone() Prototype {
// 创建一个新对象,并复制当前对象的属性
return &ConcretePrototype{
Name: p.Name,
Age: p.Age,
}
}

func (p *ConcretePrototype) String() string {
return fmt.Sprintf("Name: %s, Age: %d", p.Name, p.Age)
}

func main() {
// 创建原型对象
prototype := &ConcretePrototype{
Name: "Alice",
Age: 25,
}

// 克隆原型对象
clone := prototype.Clone().(*ConcretePrototype)

// 修改克隆对象的属性
clone.Name = "Bob"
clone.Age = 30

// 输出原型对象和克隆对象
fmt.Println("Prototype:", prototype)
fmt.Println("Clone:", clone)
}

使用原型模式,如果有引用类型,则需要考虑深拷贝和浅拷贝的问题

浅拷贝只复制对象本身而不复制其引用的对象,深拷贝则会递归地复制整个对象图。

这需要根据需求选择适当的拷贝方式

参考:
Go24种设计模式——创建型模式