design_pattern_singleton 单例模式

模式动机

对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。

如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。

一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。1

模式定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点。2

模式结构

类图3
类图

实现例子

以下是几种实现单例模式的例子4

1. 使用函数装饰器实现单例

(闭包方式)

这个实现单例模式的方式将原来类的定义隐藏在闭包函数中,通过闭包函数及其中引用的自由变量来控制类对象的生成。由于唯一的实例存放在自由变量中,而且自由变量是无法直接在脚本层进行访问的。这种方式非常隐蔽的保护实例不被修改,因此很适合用于单例模式。

def Singleton(cls):
	# 使用函数装饰器实现单例
	_instance = {}

	def inner():
		if cls not in _instance:
			_instance[cls] = cls()
		return _instance[cls]

	return inner


@Singleton
class Cls(object):
	def __init__(self):
		pass


if __name__ == '__main__':
	instance1 = Cls()
	instance2 = Cls()
	print(instance1 is instance2) # true
2. 使用类装饰器实现单例

(类作为装饰器之__call__方式)

类作为装饰器要想达到相同的效果只需要将类的对象返回,并且其对象是可以调用的。这是上面这个例子表达的一个核心思想。
这种方式写法很多,也很灵活,其思想基本上就是对被包裹对象的调用实际上调用的是类对象的__call__函数,该函数实际上是对被装饰对象的一次封装。

class Singleton(object):
	# 使用类装饰器实现单例
	def __init__(self, cls):
		self._cls = cls
		self._instance = {}

	def __call__(self):
		if self._cls not in self._instance:
			self._instance[self._cls] = self._cls()
		return self._instance[self._cls]


@Singleton
class Cls(object):
	def __init__(self):
		pass


if __name__ == '__main__':
	instance1 = Cls()
	instance2 = Cls()
	print(instance1 is instance2)  # true
3. 使用 metaclass 实现单例模式

(元类方式) 5

class Singleton(type):
    def __init__(self, *args, **kwargs):
        self.__instance = None
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            self.__instance = super().__call__(*args, **kwargs)
            return self.__instance
        else:
            return self.__instance


class Cls(metaclass=Singleton):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


if __name__ == '__main__':
    instance1 = Cls()
    instance2 = Cls()
    print(instance1 is instance2)
4. 重写创建实例的__new__方法实现单例 (推荐使用,方便)

知识点:

  • 一个对象的实例化过程是先执行类的__new__方法,如果我们没有写,默认会调用object的__new__方法,返回一个实例化对象,然后再调用__init__方法,对这个对象进行初始化,我们可以根据这个实现单例.
  • 在一个类的__new__方法中先判断是不是存在实例,如果存在实例,就直接返回,如果不存在实例就创建.
class Singleton(object):
	# 重写创建实例的__new__方法实现单例
	__instance = None

	def __new__(cls, *args, **kwargs):
		if cls.__instance is None:
			cls.__instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
		return cls.__instance


if __name__ == '__main__':
	instance1 = Singleton()
	Singleton.__instance = None
	instance2 = Singleton()
	print(instance1 is instance2) # true

线程安全

import threading


class Singleton(object):
	__instance_lock = threading.Lock()
	# 重写创建实例的__new__方法实现单例
	__instance = None

	def __new__(cls, *args, **kwargs):
		if cls.__instance is None:
			with cls.__instance_lock:
				if cls.__instance is None:
					cls.__instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
		return cls.__instance

模式优缺点

优点1

  • 提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它,并为设计及开发团队提供了共享的概念。
  • 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
  • 允许可变数目的实例。我们可以基于单例模式进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例。

缺点

  • 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。

  • 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。

  • 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致对象状态的丢失。

  • 《游戏编程模式》作者说如何规避单例模式6

题外话

  • 其他例子
class CSingleTon(object):
    """
        单例基类
    """
    pMgrObj = None
    
    def __init__(self):
        assert (self.pMgrObj is None) or (not isinstance(self.pMgrObj, self))

    @classmethod
    def Get(cls):
        if (cls.pMgrObj is None) or (not isinstance(cls.pMgrObj, cls)):
            cls.pMgrObj = cls()
        return cls.pMgrObj

    sharedManager = Get

    @classmethod
    def Release(cls):
        cls.pMgrObj = None

    releaseManager = Release
    

class Cls(CSingleTon):
    pass


if __name__ == '__main__':
    instance1 = Cls.Get()
    instance2 = Cls.Get()
    print(instance1 is instance2)
  • 单例与全局变量比较
    全局变量可以提供一个实例并提供全局访问,但不能确保只有一个实例。全局变量也会变相鼓励开发人员,用许多全局变量指向许多小对象来造成命名空间的污染。单件不鼓励这样的现象,单单件仍然可能被滥用

  • 饿汉模式、懒汉模式


  1. 《图说设计模式》 ↩︎ ↩︎

  2. 《设计模式: 可复用面向对象软件的基础》 ↩︎

  3. https://refactoringguru.cn/design-patterns/singleton ↩︎

  4. Python实现Singleton模式的几种方式 ↩︎

  5. https://python3-cookbook.readthedocs.io/ ↩︎

  6. [游戏编程模式]https://gpp.tkchu.me/singleton.html ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值