单例模式属于创建型模式(Builder Pattern),意图在于担保一个类仅有一个实例,并供应一个访问它的全局访问点。单例模式在内存中仅创建一次工具,纵然多次实例化该类,也只返回第一次实例化后的实例工具,不仅能减少不必要的内存开销,并且在减少全局的函数和变量也具有主要的意义。
实现办法上,紧张有
场景上,最常用于全局配置管理,其次在IO操作、前端交互等须要担保工具唯一的场景,也可以利用。
01

在golang中单例模式的实现办法有多种,这里先容下利用init和sync.Once办法实现线程安全的单例。
个中init函数是在文件包首次被加载的时候实行并且只实行一次(Eager Singleton,饿汉模式),sync.Once是在代码运行须要的时候实行且只实行一次(Lazy Singleton,
在golang一些server业务场景运用中,常日会用到一些resource,如常用的:DB、Redis、Logger等,这些资源的实例化工具会在每个要求中频繁的利用,如果在每个要求的处理进程中频繁创建和开释这些资源工具,则会造成较大的系统资源开销,但如果利用单例的办法创建这些资源工具则能避免这些问题,常日实际利用场景中会在main主进程中的HTTPServer携程启动前,通过init或sync.One的办法创建单例工具供应各HTTPServer携程利用,从而担保各个要求处理进程中利用同一个实例工具。
......
var (
oResource sync.Once
initFuncList = []initFunc{
mustInitLoggers, // 初始化Log
mustInitServicer, // 初始化servicer以及ral
mustInitGorm, // 初始化mysql gorm
mustInitRedis, // 初始化redis
mustInitOther, // 初始化other
}
)
type initFunc func(context.Context) func() error
// MustInit 按顺序初始化app所需的各种资源
func MustInit(ctx context.Context) (f func() error) {
oResource.Do(func() {
callbackList := make([]func() error, 0, len(initFuncList))
for _, f := range initFuncList {
callbackList = append(callbackList, f(ctx))
}
f = func() (err error) {
for i := len(callbackList) - 1; i >= 0; i-- {
if callbackList[i] != nil {
e := callbackList[i]()
if err == nil {
err = e
}
}
}
return
}
})
return
}
......
02
单例模式在配置管理中的运用在Python中,一个很普遍的运用处景便是利用单例模式来实现全局的配置管理。对付大部分的系统,常日都会有一个或者多个配置文件用于存放系统运行时所依赖的各种配置信息,在系统运行的过程中通过代码读取并解析配置文件从而得到对应的配置信息,而且在运行过程中当配置文件发生变更往后还须要实时更新对应的配置信息。
在这个场景里面,如果每次利用重新读取和加载配置,会有以下问题:
增加耗时:带来额外的韶光开销,额外的开销韶光和读取次数成正比。
增加内存:带来额外的内存开销,额外的内存占用和工具的实例个数成正比。
在这个场景里面,有一个范例的特色:须要反复获相同配置文件的内容,配置文件的内容可能会发生变更,以是这个场景就比较得当通过单例模式来实现。即在系统初始化或者首次利用配置的时候加载文件并解析天生一个配置类工具,同时这个工具会实时监听文件内容变更并更新工具的对应属性,后续每次都直策应用这个工具获取文件内容即可。这样即可办理反复读取文件初始化工具以及监听文件变更所来的额外韶光和空间开销。
以下为基于Python实现的Demo:
# runtime_conf.py
class RuntimeConf(object):
\"大众\"大众\"大众
单例模式实现的运行时全局配置类
1、用于解析配置文件并封装成工具属性方便利用
2、持续监听配置文件,发生变更后自动更新实例工具属性
\公众\"大众\"大众
def __new__(cls):
if not hasattr(cls, '_instance'):
# 1、初始化实例工具
cls._instance = super(RuntimeConf, cls).__new__(cls)
# 2、加载配置文件
cls._instance.load_config()
# 3、持续开启一个新线程持续监听文件变革,文件发生变更往后更新实例属性
cls._instance.__watch_async()
return cls._instance
def __watch_async(self):
\"大众\"大众\"大众
私有的监听配置文件方法,如果配置文件发生变更,重新读取配置文件并加载到 self.__data 属性
:return:
\"大众\公众\"大众
# 以下仅为示例思路,详细实现文件监听可复用第三方框架,例如 pyinotify
changed = False
# ......
# 如果文件发生变更,重新加载
if changed:
self.__load_config()
def __load_config(self):
\公众\"大众\公众
私有读取配置文件并加载到工具属性中
:return:
\"大众\"大众\"大众
# 读取配置文件并存储到self.__data属性
self.__data = {
\"大众key1\"大众: 1,
\"大众key2\"大众: 2
}
print(\"大众load config success\"大众)
def get(self, key):
\公众\公众\"大众
读取配置
:param key:
:return:
\"大众\"大众\"大众
return self.__data.get(key, None)
if __name__ == '__main__':
# 初始化两个工具,输出工具的内存地址,可以创造两个变量都是指向同一个内存地址,即是同一个工具
conf_1 = RuntimeConf()
conf_2 = RuntimeConf()
print(conf_1)
print(conf_2)
print(conf_1.get(\"大众key1\"大众))
print(conf_2.get(\公众key2\"大众))
03
单例模式在IO操作的运用在PHP中,单例模式一个范例的运用处景,便是IO操作,范例的有数据库、文件操作等,浸染在于掩护一个全局变量,去管理连接实例。
以范例的PHP站点为例,在标准的MVC构造下,单次网络要求相应过程中,会涉及到多个不同Model的实例化,而每个Model实例又须要进行数据库操作,这里就须要掩护全局唯一的数据库连接实例,一样平常用单例模式进行掩护。如果每个Model在实例化时,都建立新的连接,显然是不合理的,会有以下问题:
资源摧残浪费蹂躏:频繁建立连接,增加网络耗时、CPU&内存开销。
无法提交事务:多条SQL语句,不是一个连接提交,无法完全的提交数据库事务。
在这个场景下,我们可以用单例模式办理,单例类可以具备私有的布局函数,并且供应静态方法供外界获取它的实例,外部首次获取时,每次获取到的是同一个工具,由这个工具掩护数据库连接。
以下为基于PHP实现的Demo:
class DBHandler {
private static $instance = ; //私有实例
public $conn; //数据库连接
//私有布局函数
private function __construct() {
$this->conn = new PDO('hostname', 'account', 'password');
}
//静态方法,用于获取私有实例
public static function getInstance() {
if (self::$instance == ) {
self::$instance = new DBHandler();
}
return self::$instance;
}
public function fetch() {...}
}
class ModelA {
private $dbHandler;
public function __construct() {
$this->dbHandler = DBHandler->getInstance();
}
public function getA() {
return $this->dbHandler->fetch($sql);
}
}
class ModelB {
private $dbHandler;
public function __construct() {
$this->dbHandler = DBHandler->getInstance();
}
public function getB() {
return $this->dbHandler->fetch($sql);
}
}
$modelA = new ModelA();
$modelB = new ModelB();
$modelA->getA();
$modelB->getB();
04
单例模式在前端交互的运用在前端开拓中,单例模式的利用十分常见,很多第三方库和框架都运用了单例模式。比如最常用的 js 库 jQuery,它暴露了一个 jQuery 实例,多次引用都只会利用该实例工具。这样的模式,减少了全局变量的创建,并且能够避免变量冲突。
实现单例模式常见的办法有:首先创建一个类,这个类包含一个静态方法,用于创建这个类的实例工具;还存在一个标记,标识实例工具是否已经创建过,如果没有,则创建实例工具并返回;如果创建过,就直接返回顾次创建的实例化工具的引用。
在实际运用中,我们常利用单例模式来管理页面中的弹窗,避免页面中同时展现多个相互重叠的弹窗:可以创建一个 Toast 弹窗类,并初始化弹窗节点。这个类供应一个静态方法 getInstance 来创建并返回实例工具,这样业务在创建弹窗时就不须要再进行实例化的操作。业务可以通过 show 和 hide 方法来掌握弹窗的展现和隐蔽,但纵然实行多次 show 方法,也只会展现一个弹窗,由于业务利用的是同一个实例工具。这个类在页面运行时会一贯存在,除非没有了对这个类的引用,它则会被垃圾回收。
以下为基于JavaScript实现的Demo:
// 弹窗组件 toast.js
class Toast {
constructor() {
this._init();
}
// 私有方法,业务不许可直接调用该方法来创建弹窗
_init(){
const toast = document.createElement('div');
toast.classList.add('toast');
toast.innerHTML = '这是一个弹窗';
document.body.append(toast);
}
show() {
document.querySelector('.toast').style.display = 'block';
}
hide() {
document.querySelector('.toast').style.display = 'none';
}
// 静态方法,业务操作弹窗时不须要再实例化
static getInstance() {
if(!this.instance) {
this.instance = new Toast();
}
return this.instance;
}
}
// 在组件中把对唯一的实例工具 loginToast 的引用暴露出去
const toast = Toast.getInstance();
export default toast;
// 业务调用
import toast from './xxx/toast';
toast.show();
本文由高可用架构转载。技能原创及架构实践文章,欢迎通过公众年夜众号菜单「联系我们」进行投稿。