设计模式及单例模式

设计模式及单例模式

设计模式简介

​ 前人通过大量的验证,所创建出来的解决一些问题的固定高效方法

IT行业的设计模式

​ IT行业的设计模式一共有23种,分为:创建型、结构型、行为型

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式

单例模式

定义:确保一个类最多只有一个实例,并提供一个全局访问点

目的:当类中有很多非常强大的方法,我们在程序中很多地方都需要,但是这些方法有大多是绑定对象的动态方法,必须要建立对象来用。如果不做单例,会产生很多无用的对象浪费存储空间,那么单例模式就可以在整个程序就使用一个对象。

实现单例的几种方式

模块存储类和单例(最常见的一种方式)

我们在模块中写入类,然后在最后用这个类产生对象,那么我们可以通过导模块的方式访问到这个对象,从而调用类中许多强大的功能,而其他py文件在导入此模块时,由于只有第一次会运行模块代码,所以只会产生一次对象,这个对象就是单例。

# 在模块中写入
class Mysql:
    def __init__(self, ip):
        self.ip = ip

    def many_strong_func(self):
        pass
    
obj = Mysql('127.0.0.1')  # 模块中产生唯一一个对象

# 在要使用此单例的地方
# py文件1
from md import obj  # 这个md是存储单例的模块
obj.many_strong_func()  # 在其他地方使用通过对象点的方式

# py文件2
from md import obj  # 统一个程序下的第二次导入就不再运行
obj.many_strong_func()  # 仍然使用第一次导入时产生的对象

类中存储单例的方式

在定义类时,定义一个隐藏的属性用于存储类的单例地址,再定义一个创建单例的类方法,当调用者通过类方法创建对象时拿到单例,而使用一般类名+括号创建则创建新对象。

class C1:
    __instance = None  # 隐藏的单例属性

    def __init__(self, name, age):
        self.name = name
        self.age = age

    @classmethod
    def singleton(cls):  # 当调用这个类方法时会最终返回类中的单例
        if not cls.__instance:  # 如果此时单例还没有创建,则创建
            cls.__instance = cls('jason', 18)
        return cls.__instance

# 使用这个类方法的方式创建单例
obj1 = C1.singleton()
obj2 = C1.singleton()
obj3 = C1.singleton()
print(id(obj1), id(obj2), id(obj3))  # 三个对象的id一致,说明相同
# 使用正常的方式创建的是新的对象
obj4 = C1('kevin', 28)
obj5 = C1('tony', 38)
print(id(obj4), id(obj5))  # 这两个对象id不一致,说明产生新的对象

元类控制单例类的产生

用这个元类产生类时,就已经按照一定的配置同时创建了一个对象,

即在双下init方法派生,使得在原本创建类的操作同时,也产生了一个单例,

同时,为了控制对象产生时能够直接拿到单例,我们还要派生双下call方法。

class Mymeta(type):
    def __init__(cls, name, bases, dic):  # 定义类Mysql时就触发
        # 定义类时就从配置文件中取配置来造一个Mysql的实例对象出来
        cls.__instance = object.__new__(cls)  # 产生对象
        cls.__init__(cls.__instance, 'jason', 18)  # 初始化对象
        super().__init__(name, bases, dic)  # 原本的init操作

    def __call__(cls, *args, **kwargs):  # 产生对象的语句触发
        if args or kwargs:  # args或kwargs内有值 即有参时
            # 有参时表明我想得到新对象,就调用原本的call产生新对象
            return super().__call__(*args, **kwargs)  
        # 无参说明我也用之前的配置文件即可,就直接返回单例
        return cls.__instance  


class Mysql(metaclass=Mymeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age

obj1 = Mysql()
obj2 = Mysql()
print(id(obj1), id(obj2))
obj3 = Mysql('tony', 321)
obj4 = Mysql('kevin', 222)
print(id(obj3), id(obj4))

基于装饰器的单例模式

在类名上面加语法糖装饰器,相当于装饰了类被调用时的__call__,也就是针对对象产生的过程做限制,我们可以在装饰器的局部名称空间中,创建一个单例,然后在局部的局部判断有没有传值,如果没有传值,就返回我们在局部空间创建好的单例。

def singleton(cls):
    IP, PORT = '127.0.0.1', '8888'
    _instance = cls(IP, PORT)  # 创建一个单例

    def inner(*args, **kwargs):
        if args or kwargs:  # 有参就正常运行原本的创建对象
            res = cls(*args, **kwargs)
            return res
        return _instance  # 无参就视为拿我们默认的单例

    return inner  # 返回inner替换类名


@singleton  # Mysql = singleton(Mysql)
class Mysql:
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port


obj1 = Mysql()
obj2 = Mysql()
obj3 = Mysql(ip='127.0.0.1', port='8888')
print(id(obj1), id(obj2), id(obj3))

元类控制单例的产生过程

直接在__call__中修改来控制单例的产生过程,我们也可以提前在元类的call中就塞入一个配置好的单例,这里整的不一样的,让第一个按参数创建的对象成为单例,后续按参数创建就创建新对象,不传参就拿到最开始创建的单例。(当然,实战意义不大,主要是开拓一下思路)

class Singleton(type):
    def __call__(cls, *args, **kwargs):
        if not hasattr(cls, f'single'):  # 如果没有single属性,给它创一个
            cls.single = None
        if not (args or kwargs):
            if not cls.single:  # 如果没有单例和参数传入,报错提示
                raise TypeError('你没有创建过单例,请传入相关参数配置')
            # 如果没有参数但有单例,返回单例
            return cls.single
        else:  # 如果有参数
            if not cls.single:  # 如果没有单例,按照参数创建单例
                cls.single = super().__call__(*args, **kwargs)
                return cls.single
            # 如果有参数有单例,那创建新对象
            return super().__call__(*args, **kwargs)


class A(metaclass=Singleton):
    def __init__(self, name):
        self.name = name


obj1 = A('leethon')
obj2 = A('anyone else')
obj3 = A('SOME')
obj4 = A()
print(obj1.name)
print(obj2.name)
print(obj3.name)
print(obj4.name)
posted @ 2022-11-08 19:01  leethon  阅读(47)  评论(0编辑  收藏  举报