Go 设计模式-单例模式


简介

什么是单例模式?

单例模式是一种设计模式,其核心思想是确保某个类只有一个实例,并提供一个全局访问点。

本质

单例模式本质上就是全局变量

优点

  1. 全局唯一实例:确保一个类只有一个实例,并提供全局访问点。
  2. 延迟初始化:实例只在需要时才会被创建。
  3. 配置加载较为方便:可以将初始化封装在使用过程中(个人觉得比较方便,省去了写一个统一的加载文件)
  4. 线程安全:使用 sync.Once 可以确保在多线程环境下也是安全的。

缺点

  1. 全局状态:单例模式本质上就是全局变量,可能导致不可预见的副作用和状态。
  2. 违反单一职责原则:除了控制实例创建和销毁,单例类通常还承担其他职责。

使用场景

  1. 数据库连接池:确保一个应用程序创建的数据库连接数量是有限和唯一的。
  2. 日志记录器:全局只需要一个日志记录对象即可。
  3. 配置管理:当需要从一个地方管理和访问配置信息时。
  4. 硬件接口访问:如打印机、图形卡等硬件资源通常需要全局唯一访问实例。

实现方式

懒汉式

最重要的一点,懒汉式可以避免手动 init,(自动 init 也不稳妥,go 的 init 的顺序比较迷)
同时懒汉式避免了一些多依赖初始化的问题(如果使用饿汉式,顺序就是一个很大的问题)
就懒汉式初次启动慢的问题,可能更适合一些长期运行的项目。

package models

import (
	...
)

var db *gorm.DB
var once sync.Once

// todo 修改
func DB() *gorm.DB {
    //使用 sync.Once 可以有效解决线程安全问题
	once.Do(func() {
		initDao()
	})
	return db
}

// initDao 连接数据库
func initDao() {
	/* ... */
}
func main(){
    //可以这样使用,成功把配置的初始化封装
    DB().Create(...)
}

缺点

容易造成依赖循环

比如下方

var onceLog sync.Once
var log *zap.Logger{}
func Log(){
	onceLog.Do(func() {
		initLog()
	})
	return log
}

func initLog(){
    /*而这里面需要从配置文件中读取 Log 日志的存储路径,以及来设置 log 的存储位置*/
}
var onceConf sync.Once
var conf *viper.Viper{}
func Config(){
	onceLog.Do(func() {
		initConf()
	})
	return conf
}

func initConf(){

    /*这里面有个地方需要来加载日志的变化情况,此时需要用到 Log()*/
}

上面的设计显然是不合理的,当时不要把两段代码放在一起,单看可能挺合理的,我们需要时刻注意这个问题。

饿汉式

在类加载时就创建实例,确保实例的唯一性。

package main

import "fmt"

type Singleton struct{}

var instance = &Singleton{}

func GetInstance() *Singleton {
	return instance
}

func main() {
	s1 := GetInstance()
	s2 := GetInstance()
	if s1 == s2 {
		fmt.Println("s1 和 s2 是同一个实例")
	} else {
		fmt.Println("s1 和 s2 不是同一个实例")
	}
}

优点

  1. 简单易实现:代码简单,易于理解。
  2. 线程安全:由于实例是在类加载时创建的,不存在多线程同步问题。

缺点

  1. 资源浪费:如果该实例一直没有被使用,会造成资源浪费。
  2. 可能存在初始化顺序问题:如果单例依赖其他类,可能会因为初始化顺序而导致问题。

如果本文帮助到了你,帮我点个广告可以咩(o′┏▽┓`o)


评论
  目录