单例模式(Singleton) 核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
单例模式的要点有三个:
- 单例模式的类只提供私有的构造函数
- 类定义中含有一个该类的静态私有对象
- 该类提供了一个静态的共有函数用于创建或获取它本身的静态私有对象。
用途:
- 常用于程序的全局配置;
- 常用于程序引擎类;
- 用于数据的存储,保证数据的唯一性;
- 一个程序中只有一个日志输出实例
优点:
1、实例控制
单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
2、灵活性
因为类控制了实例化过程,所以类可以灵活更改实例化过程。
缺点:
1、开销
虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。
2、可能的开发混淆
使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
3、对象生成期
不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致单例类中出现悬浮引用。
单例模式包含如下角色
- Singleton:单例
时序图:
实例
在操作系统中,打印池(Print Spooler)是一个用于管理打印任务的应用程序,通过打印池用户可以删除、中止或者改变打印任务的优先级,在一个系统中只允许运行一个打印池对象,如果重复创建打印池则抛出异常。现使用单例模式来模拟实现打印池的设计。
Python中单例模式的几种实现方式
一、实现__new__方法,然后将类的一个实例绑定到类变量_instance上
如果cls._instance为None,则说明该类还没有被实例化过,new一个该类的实例,并返回;如果cls._instance不为None,直接返回_instance,代码如下:
class Singleton(object):
def __new__(cls, *args, **kwargs):
if not hasattr(cls,'_instance'):
orig=super(Singleton,cls)
cls._instance=orig.__new__(cls,*args,**kwargs)
return cls._instance
class MyClass(Singleton):
a=1
one=MyClass()
two=MyClass()
# one和two完全相同,可以用id(),==,is检查
print(one.a) # 1
print(id(one)) # 2565285375728
print(id(two)) # 2565285375728
print(one == two) # True
print(one is two) # True
二、使用metaclass(元类)的python高级用法
"""
class Singleton中的__init__在Myclass声明的时候被执行Myclass=Singleton()
Myclass()执行时,最先执行父类的__call__方法(object,Singleton都作为Myclass的父类,
根据深度优先算法,会执行Singleton中的__call__(),Singleton中的__call__()写了单例模式)
"""
class Singleton(type):
def __init__(self, name, bases, dict):
super(Singleton,self).__init__(name,bases, dict)
self._instance = None
def __call__(self, *args, **kwargs):
if self._instance is None:
self._instance = super(Singleton,self).__call__(*args, **kwargs)
return self._instance
class MyClass(object,metaclass=Singleton):
a = 1
one=MyClass()
two=MyClass()
print(id(one)) # 1553247294800
print(id(two)) # 1553247294800
print(one == two) # True
print(one is two) # True
三、使用python的装饰器(decorator)实现单例模式
这是一种更Pythonic的方法;单例类本身的代码不是单例的,通过装饰器使其单例化。
_instance = {}
使用不可变的类地址作为键,其实例作为值,每次创造实例时,首先查看该类是否存在实例,存在的话直接返回该实例即可,否则新建一个实例并存放在字典中。
def singleton(cls, *args, **kwargs):
instances = {}
def _singleton():
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return _singleton
@singleton
class MyClass3(object):
a = 1
one = MyClass3()
two = MyClass3()
print(id(one)) # 2880466769232
print(id(two)) # 2880466769232
print(one == two) # True
print(one is two) # True
python的单例模式__new__()在__init__()之前被调用,用于生产实例对象。利用这个方法和类的属性的特点可以实现设计模式的单例模式。单例模式是指创建唯一对象,单例模式设计的类只能实例化一个对象。
class Singleton(object):
__instance=None
def __init__(self):
pass
def __new__(cls, *args, **kwargs):
if Singleton.__instance is None:
Singleton.__instance=object.__new__(cls,*args, **kwargs)
return Singleton.__instance
one=Singleton()
two=Singleton()
print(id(one)) # 2488569943376
print(id(two)) # 2488569943376
print(one == two) # True
print(one is two) # True
四、@classmethod实现单例模式
class Singleton:
@classmethod
def getSingletonInstanceObject(cls, *args, **kwargs):
if not hasattr(cls, "ins"):
insObject = cls(*args, **kwargs)
setattr(cls, "ins", insObject)
return getattr(cls, "ins")
if __name__ == "__main__":
ins = Singleton.getSingletonInstanceObject()
print(id(ins))
ins = Singleton.getSingletonInstanceObject()
print(id(ins))
实际应用
假如,我们存储数据工具是SQL Server,我们需要通过host、user、passwd来连接数据库进行读取数据,这时候就需要一次认证,多次调用。
普通模式:
# 一个连接SQL的类
class SqlClient(object):
def __init__(self, host, user, passwd):
self.host = host
self.user = user
self.passwd = passwd
self.register()
def register(self):
self.info = "{}--{}---{}".format(self.host, self.user, self.passwd)
def select(self):
print("SELECT * FROM {}".format(self.host))
SqlClient中有3个方法,__init__用于初始化参数,register是认证SQL客户端,select是执行SQL语句的操作。
后面我们会在不同的地方查找数据,也就是在多个地方需要调用SqlClient类的select方法,有两种方法:1.反复实例化、反复认证,2.把实例化后的对象作为参数传入到每个用到select的函数里。
1.反复实例化、反复认证
host = "10.293.291.19"
user = "admin"
passwd = "666666"
def use_data_1():
sql_client = SqlClient(host, user, passwd)
sql_client.select()
def use_data_2():
sql_client = SqlClient(host, user, passwd)
sql_client.select()
def use_data_3():
sql_client = SqlClient(host, user, passwd)
sql_client.select()
use_data_1()
use_data_2()
use_data_3()
# 输出
SELECT * FROM 10.293.291.19
SELECT * FROM 10.293.291.19
SELECT * FROM 10.293.291.19
在use_data_1、use_data_2、use_data_3三处使用到了SQL选择工具,每一次我们都要重新实例化SqlClient
2.把实例化后的对象作为参数传入到每个用到select的函数里
host = "10.293.291.19"
user = "admin"
passwd = "666666"
def use_data_1(sql_client):
sql_client.select()
def use_data_2(sql_client):
sql_client.select()
def use_data_3(sql_client):
sql_client.select()
sql_client = SqlClient(host, user, passwd)
use_data_1(sql_client)
use_data_2(sql_client)
use_data_3(sql_client)
我们可以先对实例化SqlClient,然后作为参数传入到每一个用到SQL工具的地方。虽然在代码简洁性方面比第一种方法优化了不少,但是传递的参数会很多,在开发中应该尽量少传参数,尤其是链式调用的函数。
单例模式:
class Singleton(object):
def __new__(cls, *args, **kw):
if not hasattr(cls, '_instance'):
orig = super(Singleton, cls)
cls._instance = orig.__new__(cls)
return cls._instance
class SqlClient(Singleton):
info = None
def register(self, host, user, passwd):
self.info = "{}--{}--{}".format(host, user, passwd)
def select(self):
print(self.info)
通过继承Singleton实现SqlClient的单例模式,我们只需要调用register一次,用于认证客户端,然后后期每次重新实例化都是指向的同一个实例,也就是已经认证过的示例,我们后面任何其他地方调用的地方直接使用select方法即可。
def use_data_1():
SqlClient().select()
def use_data_2():
SqlClient().select()
def use_data_3():
SqlClient().select()
SqlClient().register(host, user, passwd)
use_data_1()
use_data_2()
use_data_3()
C++实现单例模式
#include <iostream>
#include "Singleton.h"
using namespace std;
int main(int argc, char *argv[])
{
Singleton * sg = Singleton::getInstance();
sg->singletonOperation();
return 0;
}
///
// Singleton.cpp
// Implementation of the Class Singleton
// Created on: 02-十月-2014 17:24:46
// Original author: colin
///
#include "Singleton.h"
#include <iostream>
using namespace std;
Singleton * Singleton::instance = NULL;
Singleton::Singleton(){
}
Singleton::~Singleton(){
delete instance;
}
Singleton* Singleton::getInstance(){
if (instance == NULL)
{
instance = new Singleton();
}
return instance;
}
void Singleton::singletonOperation(){
cout << "singletonOperation" << endl;
}
懒汉版(Lazy Singleton)
单例实例在第一次被使用时才进行初始化,这叫做延迟初始化。
class Singleton
{
private:
static Singleton* instance; //静态的 私有的
private:
Singleton() {};
~Singleton() {};
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
public:
static Singleton* getInstance()
{
if(instance == NULL)
instance = new Singleton();
return instance; //如果非空则new一个对象 后者返回原来的两个对象(所以保证了只有一个对象生成)
}
};
// init static member
Singleton* Singleton::instance = NULL;
Lazy Singleton存在内存泄露的问题,因为没有释放_instance指针,有两种解决方法:
- 使用智能指针
- 使用静态的嵌套类对象
对于第二种解决方法,代码如下:
// version 1.1
class Singleton
{
private:
static Singleton* instance;
private:
Singleton() { };
~Singleton() { };
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
private:
class Deletor {
public:
~Deletor() {
if(Singleton::instance != NULL)
delete Singleton::instance;
}
};
static Deletor deletor;
public:
static Singleton* getInstance() {
if(instance == NULL) {
instance = new Singleton();
}
return instance;
}
};
// init static member
Singleton* Singleton::instance = NULL;
在程序运行结束时,系统会调用静态成员deletor的析构函数,该析构函数会删除单例的唯一实例。使用这种方法释放单例对象有以下特征:
- 在单例类内部定义专有的嵌套类。
- 在单例类内定义私有的专门用于释放的静态成员。
- 利用程序在结束时析构全局变量的特性,选择最终的释放时机。
如果在多线程环境下,因为“if(instance == NULL)”并不是原子的,会存在线程安全问题(如果一个线程刚刚判断了指针为空,这时另一个线程的优先级更高或者其它原因,打断了原来线程的执行,再次判断指针也会为空,所以会出现两个实例)下面为多线程环境下的懒汉式单例模式:
class Singleton
{
private:
static Singleton* m_instance;
Singleton(){}
public:
static Singleton* getInstance();
};
Singleton* Singleton::getInstance()
{
if(NULL == m_instance)
{
Lock();//借用其它类来实现,如boost
if(NULL == m_instance)
{
m_instance = new Singleton;
}
UnLock();
}
return m_instance;
}
饿汉版(Eager Singleton)
指单例实例在程序运行时被立即执行初始化
//Singleton.h
#pragma once
class Singleton {
public:
static Singleton* getInstance();
private:
Singleton();
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
static Singleton* instance_;
};
//Singleton.cpp
#include <iostream>
#include "Singleton.h"
Singleton* Singleton::instance_ = new Singleton();
Singleton::Singleton() {
}
Singleton::Singleton(const Singleton&) {
}
Singleton &
Singleton::operator=(const Singleton&) {
}
Singleton *
Singleton::getInstance() {
return instance_;
}
与懒汉式单例模式不同之处是,在全局作用域进行单例类的实例化,并用此实例初始化单例类的静态成员指针instance_。
程序运行初期就进行了单例类实例化,不存在上述的线程安全问题。
如何选择懒汉和饿汉模式:
特点与选择:
懒汉:在访问量较小时,采用懒汉实现。这是以时间换空间。
饿汉:由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。
对象释放问题
上边的程序中只有new,却没有delete,也就是说只有内存申请而没有内存释放,会不会有内存泄漏?
一般情况下,单例类的实例都是常驻内存的,一直存在于进程的生命周期,因此不需要手动释放。如果的确需要释放实例占用的内存,一定不能在单例类的析构函数中进行delete操作,这样会造成无限循环,可以考虑增加一个destroy方法用于释放内存,或者在单例类中定义一个内嵌的垃圾回收类,