设计模式是软件开发中的一些通用解决方案,它们是经过验证的,可重用的设计思想。这里记录了常见的设计模式,总的来说可以分为三类:

  • 创造型:关注对象的创建过程,以灵活高效的方式创建对象,而不直接适用new操作符或者依赖具体类的方法来创建对象。
  • 结构型:关注类和对象的组合方式,旨在通过集成和接口实现更加高效的接口组织。
  • 行为型:关注对象交互和职责分配,旨在定义对象之间的通信方法和算法。

创造型

关注对象的创建过程,以灵活高效的方式创建对象,而不直接适用new操作符或者依赖具体类的方法来创建对象,记忆为“单工建原抽”。

单例模式

单例模式确保一个类只有一个实例,并提供一个全局访问点。适用于需要全局唯一对象的场景。例如:配置管理器、日志记录器、线程池等。Golang中,单例对象在结构体首次初始化时创建,根据创建的时机可以分为:

  • 饿汉式单例:在程序启动时就创建实例。
  • 懒汉式单例:实例第一次被使用时才会被创建。

接下来考虑它们的协程安全性。

  • 创建时的协程安全:饿汉式单例在程序启动的主协程创建,是协程安全的。对于懒汉式单例,如果多个协程都来创建懒汉式单例,就不能保证只有一个单例对象,因此简单的懒汉式单例并不是协程安全的。设计模式重点关注这里的线程和协程安全。
  • 对象运行时的协程安全性:通过协程安全的读写锁保证互斥访问,不是设计模式关注的重点。

饿汉式单例

协程安全,不延迟加载。

package singleton

type Singleton struct{}

var instance Singleton{}

func GetInstance()*Singleton{
    return instance
}

懒汉式单例

非协程安全,延迟加载,适用于单协程情况。一般在instance==nil的时候选择创建懒汉单例,下面是一种协程不安全的情况,执行后可以看到许多地址和内容都不一样的单例。

package main

import (
	"fmt"
	"time"
)

type Singleton struct {
	val int
}

var instance *Singleton // 单例实例

// GetInstance 返回单例实例
func GetInstance(v int) *Singleton {
	if instance == nil { // 如果实例不存在,则创建
		time.Sleep(10 * time.Millisecond) // 模拟耗时操作
		instance = &Singleton{val: v}
	}
	return instance
}

func main() {
	for i := 0; i < 10; i++ {
		go func(i int) {
			singleton := GetInstance(i)
			fmt.Printf("Singleton instance: %p, value: %d\n", singleton, singleton.val)
		}(i)
	}

	// 防止主协程退出
	time.Sleep(1000 * time.Second)
}

结果为:

Singleton instance: 0xc000100000, value: 0
Singleton instance: 0xc00000a0c8, value: 5
Singleton instance: 0xc00000a0e0, value: 9
Singleton instance: 0xc00000a0e8, value: 8
Singleton instance: 0xc00000a0f0, value: 7
Singleton instance: 0xc00000a0f8, value: 6
Singleton instance: 0xc00000a100, value: 3
Singleton instance: 0xc00000a108, value: 4
Singleton instance: 0xc00000a110, value: 2
Singleton instance: 0xc00000a118, value: 1

如果要保证懒汉式单例的协程安全,需要使用互斥锁sync.Mutex

使用互斥锁实现协程安全的懒汉单例

适用一个全局互斥锁sync.Mutex来控制每一个协程对于instance变量的访问。

package main

import (
	"fmt"
	"sync"
	"time"
)

type Singleton struct {
	val int
}

var (
	instance *Singleton // 单例实例
	m        sync.Mutex // 互斥锁
)

// GetInstance 返回单例实例
func GetInstance(v int) *Singleton {
	m.Lock()
	defer m.Unlock()
	if instance == nil { // 如果实例不存在,则创建
		time.Sleep(1 * time.Millisecond) // 模拟耗时操作
		instance = &Singleton{val: v}
	}
	return instance
}

func main() {
	for i := 0; i < 10; i++ {
		go func(i int) {
			singleton := GetInstance(i)
			fmt.Printf("Singleton instance: %p, value: %d\n", singleton, singleton.val)
		}(i)
	}

	// 防止主协程退出
	time.Sleep(1000 * time.Second)
}

使用sync.Once来保证某些代码只执行一次,保证懒汉单例协程安全

无论有多少个协程调用GetInstance函数,其中one.Do的代码只会执行一次:

package main

import (
	"fmt"
	"sync"
	"time"
)

type Singleton struct {
	val int
}

var (
	instance *Singleton // 单例实例
	one      sync.Once  // 确保单例实例只被创建一次
)

// GetInstance 返回单例实例
func GetInstance(v int) *Singleton {
	if instance == nil { // 如果实例不存在,则创建
		time.Sleep(1 * time.Millisecond) // 模拟耗时操作
		fmt.Println("GetInstance v:", v)
		one.Do(func() {
			instance = &Singleton{val: v}
		}) //只会执行一次
	}
	return instance
}

func main() {
	for i := 0; i < 10; i++ {
		go func(i int) {
			singleton := GetInstance(i)
			fmt.Printf("Singleton instance: %p, value: %d\n", singleton, singleton.val)
		}(i)
	}

	// 防止主协程退出
	time.Sleep(1000 * time.Second)
}

工厂模式

工厂模式封装具体产品类的新建过程,只需要给工厂对象传递一个参数,根据不同参数就能获取一个新的产品对象,这个返回的值应该定义为一个接口,所有的产品类应该实现这个接口。 一般来说有四个组成:抽象产品接口、具体产品类、抽象工厂接口、具体工厂类。如果工厂不是很复杂或者不是很多,可以直接使用下面的简单工厂。

package main

import "fmt"

type Product interface {
	Use() string
}

type ProductA struct{}

func (a *ProductA) Use() string {
	return "using a"
}

type ProductB struct{}

func (b *ProductB) Use() string {
	return "using b"
}

//简单工厂
type SimpleFactory struct{}

func (f SimpleFactory) CreateProduct(t string) Product {
	switch t {
	case "a":
		return &ProductA{}
	case "b":
		return &ProductB{}
	default:
		return nil
	}
}

func main() {
	f := SimpleFactory{}
	a := f.CreateProduct("a")
	b := f.CreateProduct("b")
	fmt.Println(a.Use())
	fmt.Println(b.Use())
}

建造者模式(生成器模式)

当我们需要逐步构造包含多个部分和属性的复杂对象的时候需要使用到建造者模式。建造者模式通常包括一下几个角色:产品、建造者和指挥者。这种模式很适合用于做点餐系统和配置生成器。通过不同的建造者,来创建不同的实例。

type Meal struct{
	drink string
	eat string
}//产品类

type Builder interface{
	setDrink()
	setEat()
}//抽象建造者

type HealthyBuilder struct{
	meal Meal
}//创建健康搭配的构造者

func(b HealthyBuilder)setDrink(){
	b.meal=water
}
func(b HealthyBuilder)setEat(){
	b.meal=rice
}
type MealDirector struct{
	builder Builder
}//指挥者,实例化不同的builder得到不同的meal
func(d MealDirector)ConstructMeal(){
	d.builder.setDrink()
	d.builder.setEat()
}

原型模式

原型模式通过复制现有对象来创建新的对象,而不是通过实例化类来创建对象。适用于创建对象成本比较高或者比较复杂的场景。复制可以是浅拷贝或者深拷贝,取决于需求。

type ProtoType interface{
	Clone() ProtoType
}
type ConcreteProtoType struct{
	Field1 string
	Field2 string
}//原型类
func(p *ConcreteProtoType)Clone()ProtoType{
	return &ConcreteProtoType{p.Field1,p.Field2}
}
func main(){
	p1:=&ConcreteProtoType{"ni","hao"}//具体原型
	p2:=p1.Clone()(*ConcreteProtoType)//接口类型通过类型断言转换为一个具体类型
}

结构型

行为型