设计模式(Design Pattern)是软件工程中针对常见问题的通用解决方案。
它们不是具体的代码,而是经过验证的最佳实践,帮助开发者设计出灵活、可维护和可扩展的软件系统
使用设计模式的好处
- 面试
- 提高代码复用性,写出高质量代码
- 前人总结的最佳实践,在合适的地方用合适的设计模式,可以事半功倍
创建型模式
在 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 }
type MySQL struct{}
func (m *MySQL) Connect() string { return "Connected to MySQL" }
type PostgreSQL struct{}
func (p *PostgreSQL) Connect() string { return "Connected to PostgreSQL" }
type DatabaseFactory interface { CreateDatabase() Database }
type MySQLFactory struct{}
func (m *MySQLFactory) CreateDatabase() Database { return &MySQL{} }
type PostgreSQLFactory struct{}
func (p *PostgreSQLFactory) CreateDatabase() Database { return &PostgreSQL{} }
func UseDatabase(factory DatabaseFactory) { db := factory.CreateDatabase() fmt.Println(db.Connect()) } func main() { mysqlFactory := &MySQLFactory{} UseDatabase(mysqlFactory)
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"
type DBConnection interface { Connect() string }
type DBCommand interface { Execute(query string) string }
type MySQLConnection struct{}
func (m *MySQLConnection) Connect() string { return "Connected to MySQL" }
type MySQLCommand struct{}
func (m *MySQLCommand) Execute(query string) string { return fmt.Sprintf("MySQL executing query: %s", query) }
type PostgreSQLConnection struct{}
func (p *PostgreSQLConnection) Connect() string { return "Connected to PostgreSQL" }
type PostgreSQLCommand struct{}
func (p *PostgreSQLCommand) Execute(query string) string { return fmt.Sprintf("PostgreSQL executing query: %s", query) }
type DBFactory interface { CreateConnection() DBConnection CreateCommand() DBCommand }
type MySQLFactory struct{}
func (m *MySQLFactory) CreateConnection() DBConnection { return &MySQLConnection{} }
func (m *MySQLFactory) CreateCommand() DBCommand { return &MySQLCommand{} }
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() { mysqlFactory := &MySQLFactory{} UseDatabase(mysqlFactory)
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"
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 }
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"
type Prototype interface { Clone() Prototype }
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种设计模式——创建型模式