python设计模式【1】-单例模式

  1. UML类图简介
  2. 设计模式的分类
  3. 面向对象的设计原则
  4. python设计模式【1】-单例模式
  5. python设计模式【2】-工厂模式
  6. python设计模式【3】-门面模式
  7. python设计模式【4】-代理模式
  8. python设计模式【5】-观察者模式
  9. python设计模式【6】-命令模式
  10. python设计模式【7】-模板方法模式
  11. python设计模式【8】-模型·视图·控制器-复合模式
  12. python设计模式【9】-状态模式

单例模式的理解

保证只有一个特定的类型对象,并提供全局访问点
目的:

  • 确保类有且只有一个对象被创建
  • 为对象提供一个访问点,以使程序可以全局访问该对象。
  • 控制共享资源的并行访问。

应用范围: 日志记录,数据库操作,打印机后台处理程序等。
例如:希望使用一个日志类对象,将多个服务的日志信息存储到同一个日志文件中。
下面是单例模式的UML图:

python实现经典的单例模式

# coding=utf-8

class Single(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, 'instance'):
            cls.instance = super(Single, cls).__new__(cls)
        return cls.instance

if __name__ == '__main__':
    s = Single()
    print('object created', s)
    s1 = Single()
    print('object created', s1)

输出结果是:

object created <__main__.Single object at 0x000001DA2CB66A20>
object created <__main__.Single object at 0x000001DA2CB66A20>

通过覆盖__new__()方法来控制对象的创建。s就是__new__()方法创建的,但是在创建之前会先检测对象是否已经创建过。

单例模式中的懒汉实例化

单例模式的用例之一就是懒汉式实例化。我们在创建或者导入模块的时候有时会无意间创建一些对象,但是我们当时根本用不到它。懒汉式的实例化能够确保在实际使用时才会创建对象。

# coding=utf-8

class Single:
    __instance = None
    def __init__(self):
        if not Single.__instance:
            print(f"__init__ method called.")
        else:
            print(f"Instance already created :", self.get_instance())

    @classmethod
    def get_instance(cls):
        if not cls.__instance:
            cls.__instance = Single()
        return cls.__instance

if __name__ == '__main__':
    s1 = Single()
    s1.get_instance()
    s2 = Single()

输出结果:

__init__ method called.
__init__ method called.
Instance already created : <__main__.Single object at 0x000002AC045832E8>

说明:
懒汉模式下,最初在__init__成员函数中并没有做对应的初始化操作。实际上对象的初始化创建发生在get_instance()函数中。

模块级别的单例模式

默认的情况下,所有的模块都是单例的,这是python的导入行为决定的。

python通过下列方式来导入模块:

  • 检查一个python模块是否已经导入。
  • 如果已经导入,则返回该模块的对象。如果没有导入,则导入该模块,并实例化。

所以,当模块被导入的时候他就会初始化。然而当同一个模块被再次导入的时候,它不会再次初始化,因为单例模式只能有一个对象,所以他会返回同一个对象。

Monostate(单态)单例模式

Monostate关键是让实例共享相同的状态。
下面的代码中,我们将类变量__shared_state赋值给了变量__dict__。然后我们创建了两个实例b1和b2,我们得到的是两个不同的对象。然而,对象的状态确实相同的。现在对象b1的状态改变了b2的状态也会相应发生改变。

# coding=utf-8

class Borg:
    __shared_state = {"1": "2"}
    def __init__(self):
        self.x = 1
        self.__dict__ = self.__shared_state

if __name__ == '__main__':
    b1 = Borg()
    b2 = Borg()
    b1.x = 4

    print(f'borg object [b1]: {b1}')
    print(f'borg object [b2]: {b2}')
    print(f"object state [b1]: {b1.__dict__}, {id(b1.__dict__)}")
    print(f"object state [b2]: {b2.__dict__}, {id(b2.__dict__)}")

输出结果是:

borg object [b1]: <__main__.Borg object at 0x000001941CB5A588>
borg object [b2]: <__main__.Borg object at 0x000001941CB5A630>
object state [b1]: {'1': '2', 'x': 4}, 1735646939248
object state [b2]: {'1': '2', 'x': 4}, 1735646939248

单例和元类

元类的概念这里就不做介绍了,简单讲就是创建类的类。类在实例化时,他对应的元类的__call__方法将被调用。

# coding=utf-8

class MetaSingle(type):
    _instanes = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instanes:
            cls._instanes[cls] = super(MetaSingle, cls).__call__(*args, **kwargs)
        return cls._instanes[cls]

class Logger(metaclass=MetaSingle):
    pass

if __name__ == "__main__":
    log1 = Logger()
    log2 = Logger()
    print(log1)
    print(log2)

输出结果:

<__main__.Logger object at 0x0000014AC8295390>
<__main__.Logger object at 0x0000014AC8295390>

元类控制着对象的实例化,前面的思路同样适用于单例设计模式。由于元类对类创建和对象实例化有更多的控制权,所以它可以用于创建单例。(注意:为了控制类的创建和初始化,元类将覆盖__new__和__init__方法。)

单例和装饰器

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
cls1 = Cls()
cls2 = Cls()
print(id(cls1) == id(cls2))

实例1

数据库应用程序的单例应用。不同服务的共享资源是数据库本身,我们需要注意一下几点:

  • 数据库中操作一致性,一个操作不能与其他操作发生冲突
  • 优化数据库的操作,提高内存和CPU的利用率。
# coding=utf-8

import sqlite3

class MetaSingle(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class DataSet(metaclass=MetaSingle):
    connection = None
    def connect(self):
        if self.connection is None:
            self.connection = sqlite3.connect('db.sqlite3')
            self.cur_obj = self.connection.cursor()
        return self.cur_obj

if __name__ == "__main__":
    db1 = DataSet().connect()
    db2 = DataSet().connect()

    print(f"db1 cur: {db1}")
    print(f"db2 cur: {db2}")

输出结果:

db1 cur: <sqlite3.Cursor object at 0x000001F4DC827F10>
db2 cur: <sqlite3.Cursor object at 0x000001F4DC827F10>

说明:

  1. 使用MetaSIngle创建了一个元类,python的__call__可以通过元类创建单例模式。
  2. 数据库类DataSet使用元类装饰后,其行为就变为了单例模式。因此,数据库被实例化时只创建了一个对象。
  3. 当应用程序调用数据库执行操作时,它会多次实例化数据库类,但是只创建了一个对象。同时因为只有一个对象,所以对服务器的调用时同步的,还能节约系统资源。
    **注:**假如我们要开发的不是单个Web应用程序,而是集群化的情形,即多个Web应用共享单个数据库。当然,单例在这种情况下好像不太好使,因为每增加一个Web应用程序,就要新建一个单例,添加一个新的对象来查询数据库。这导致数据库操作无法同步,并且要耗费大量的资源。在这种情况下,数据库连接池比实现单例要好得多。

实例2

创建 Health Check类,它作为单例实现。维护一个被监控的服务器列表,可以删除服务器,和添加服务器。
使用 addserver()方法将服务器添加到列表中,通过迭代对这些服务器的运行状况进行检查。之后,changeserver()方法会删除最后一个服务器,并向计划进行运行状况检查的基础设施中添加一个新服务器。因此,当运行状况检查进行第二次迭代时,它会使用修改后的服务器列表。所有这一切都可以借助单例模式来完成。当添加或删除服务器时,运行状况的检查工作必须由了解基础设施变动情况的同一个对象来完成。

# coding=utf-8

class HealthCheck(object):
    _instance = None
    _servers = []
    def __new__(cls, *args, **kw_args):
        if cls._instance is None:
            cls._instance = super(HealthCheck, cls).__new__(cls, *args, **kw_args)
        return cls._instance

    def __init__(self, *args, **kw_args):
        print('class init ...')  # 注意单例模式下init也会被各自调用,注意初始化的相关问题

    def add_servers(self):
        self._servers.append('server 1')
        self._servers.append('server 2')
        self._servers.append('server 3')
        self._servers.append('server 4')

    def change_server(self):
        self._servers.pop()
        self._servers.append('server 5')

    def __getitem__(self, index):
        return self._servers[index]

    def __len__(self):
        return len(self._servers)

if __name__ == "__main__":
    hc1 = HealthCheck()
    print(f'health cheak for hc1')
    hc1.add_servers()
    for s in hc1: print(s)

    hc2 = HealthCheck()
    print(f'health cheak for hc2')
    hc2.change_server()
    for s in hc2: print(s)

输出结果:

class init ...
health cheak for hc1
server 1
server 2
server 3
server 4
class init ...
health cheak for hc2
server 1
server 2
server 3
server 5

单例模式的缺点

虽然单例模式在许多情况下效果很好,但这种模式仍然存在一些缺陷。由于单例具有全局访问权限,因此可能会出现以下问题。

  • 全局变量可能在某处已经被误改,但是开发人员仍然认为它们没有发生变化,而该变量还在应用程序的其他位置被使用。
  • 可能会对同一对象创建多个引用。由于单例只创建一个对象,因此这种情况下会对同一个对象创建多个引用。
  • 所有依赖于全局变量的类都会由于一个类的改变而紧密耦合为全局数据,从而可能在无意中影响另一个类。

提示:
在本章中,我们学习了关于单例的许多内容。对于单例模式来说,以下几点需要牢记

  • 在许多实际应用程序中,我们只需要创建一个对象,如线程池、缓存、对话框、注册表设置等。如果我们为每个应用程序创建多个实例,则会导致资源的过度使用。单例模式在这种情况下工作得很好。
  • 单例是一种经过时间考验的成熟方法,能够在不带来太多缺陷的情况下提供全局访问点。当然,该模式也有几个缺点。当使用全局变量或类的实例化非常耗费资源但最终却没有用到它们的情况下,单例的影响可以忽略不计。

参考:

《python设计模式》(第2版)https://www.epubit.com/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值