#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# author: bigfoolliu


"""
单例模式

- 保证一个类只有一个实例(控制某些共享资源,例如数据库或文件的访问权限)，并提供一个访问该实例的全局节点
- 运作方式：如果你创建了一个对象，同时过一会儿后你决定再创建一个新对象，此时你会获得之前已创建的对象，而不是一个新对象
- 允许在程序的任何地方访问特定对象，但是它可以保护该实例不被其他代码覆盖

模式结构：

1. 单例, Singleton，该类申明了一个名为`getInstance`的方法来返回其所属类的相同实例


使用场景：

- 程序中的某个类对于所有客户端只有一个可用的实例
- 需要严格的控制全局变量


实现方式：

1. 类中添加一个私有静态成员变量来保存单例实例
2. 申明一个公有静态构建方法来获取单例实例
3. 在静态方法中实现"延迟初始化", 首次被调用时创建一个新对象并将其存储在静态成员变量中, 此后该方法每次被调用时都返回该实例
4. 将类的构造函数设为私有，类的静态方法仍能调用构造函数，但是其他对象不能调用
5. 检查客户端代码，将对单例的构造函数的调用替换为对其静态构建方法的调用

优缺点：

- 保证实例为单个以及全局访问,仅在首次访问时初始化
- 可能掩盖了不良设计，如程序各组件之间了解过多
- 在多线程的环境下要特殊处理，避免多线程多次创建单例对象
- 单元测试麻烦一些
"""

from threading import Lock, Thread

###############################################################################
# 示例一
# 基础单例, 单例类可以使用基类，装饰器，元类几种方式实现，使用元类更合适一些
###############################################################################


class SingletonMeta1(type):
    """单例类元类，所有元类为该类的类，其实例都是单例"""
    _instance = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instance:
            cls._instance[cls] = super().__call__(*args, **kwargs)
        return cls._instance[cls]


class Singleton1(metaclass=SingletonMeta1):
    """单例类"""

    def operation(self):
        pass


###############################################################################
# 示例二
# 线程安全单例，必须在创建首个单例对象时对线程进行同步
###############################################################################

class SingletonMeta2(type):
    """线程安全单例类元类"""
    _instance = {}
    _lock: Lock = Lock()  # 锁对象属性，用于在首次访问单例的时候同步线程

    def __call__(cls, *args, **kwargs):
        with cls._lock:
            if cls not in cls._instance:
                cls._instance[cls] = super().__call__(*args, **kwargs)
        return cls._instance[cls]


class Singleton2(metaclass=SingletonMeta2):
    value: str = None  # 该属性仅用来测试

    def __init__(self, value):
        self.value = value

    def operation(self):
        pass


def test_singleton2(value: str):
    """线程函数"""
    singleton = Singleton2(value=value)
    print(singleton.value)


def main():
    s1 = Singleton1()
    s2 = Singleton1()
    print(id(s1), id(s2))  # id相同则说明是同一个实例

    # 最终结果均为value1则正确
    process1 = Thread(target=test_singleton2, args=("value1",))
    process2 = Thread(target=test_singleton2, args=("value2",))
    process1.start()
    process2.start()


if __name__ == "__main__":
    main()
