单例模式

剑指office面试题2

Posted by 周自横 on April 22, 2020

实现单例(Singleton)模式

定义

单例模式:确保一个类只有一个实例,并提供一个全局访问点

注意:

  • 可以直接访问不需要实例化该类的对象
  • 单类例只能有一个实例
  • 单类例必须自己创建自己的唯一的实例
  • 单类例必须给所有其他对象提供这一实例

主要解决:一个全局使用的类频繁的创建与销毁

何时使用:当你像控制实例数目,节省系统资源的时候

如何解决:判断系统是否有这个单例,如果有则返回,如果没有则创建

关键代码:构造函数是私有的

应用实例:

  • 一个班级只有一个班主任
  • Windows是多线程多进程的,在操作一个文件的时候,就不可避免出现多个进程或线程同时操作一个文件的现想,所以所有文件的处理必须通过唯一的实例来进行
  • 一些设备管理器常设计为单例模式.比如电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件

优点:

  • 在内存只有一个实例,减小了内存的开销,尤其是频繁的创建和销毁实例的时候
  • 避免对资源的多重占用(如文件操作)

缺点:

没有接口,不能继承,与单一职责原则相冲突,一个类应该只关系内部的逻辑,而不关心外面怎么实例化.

程序与理解

class SingletonMetaclass(type):
    def __init__(self, *args, **kwargs):
        self.__instance = None
        super().__init__(*args, **kwargs)
	#_call__方法的作用是当实例被作为一个函数调用时,而被调用
    # 类创建的实例,若是使用  名称() 就是  名称.__call__()
    def __call__(self, *args, **kwargs):
        print("调用")
        # __instance是特殊的命名规则,属于“名称修饰”,双下划线会让解释器重写属性名称,避免与子类中的命名冲突,由于这个原因若是实例化之后直接使用该属性因为重写命名的缘故会提示没有该属性
        if self.__instance is None:
            #这一步实际上就是正常类被实例化的第一步,正常的是class(*args, **kwargs)就是class.__call__(*args, **kwargs),由于class是type的一个实例,所以使用的是type.__call__(class, *args, **kwargs),调用type.__new__(class, *args, **kwargs)创建内存空间,返回一个空的对象
            self.__instance = super().__call__(*args, **kwargs)
            return self.__instance
        else:
            return self.__instance

# Example  完成了对SingletonMetaclass的初始化
class Spam(metaclass=SingletonMetaclass):
    def __init__(self):
        print('Creating Spam')
#先实现的是可调用对象然后再实现了初始化
a = Spam()#调用\n Creating Spam
b = Spam()#调用
a is b # True
type(Spaem)# __main__.SingletonMetaclass
type(a)# __main__.Spaem

理解

看上面的代码可以发现,Spam已经是一个实例了,也就是创建类Spam时,实际上是创建实例Spam = SingletonMetaclass(),这一点可以从它的类型可以看出来,也可以看到在创建Spam的实例的时候,首先是调用了SingletonMetaclass__call__

另外在创建了两次实例的时候,只有在第一次创建的时候调用了类的初始化函数,由于单例类的原因,认为已经初始化过了

所以该单例实现的原因在于,SingletonMetaclass创建了一个实例Spam类,完成了SingletonMetaclass的初始化,在Spam创建实例的时候,首先完成由于()的原因调用了Spam的模板类SingletonMetaclass__call__,由于self指向的是实例自身也就是Spam所以属性__instance得到值,之后完成完成了Spam的初始化。之后再创建实例的时候,首先完成的还是可调用对象,这时候由于还是Spam实例去调用,和之前是同一个实例,所以self.__instance指向的是同一个实例的同一个值,所以没有完成__new_创建内存空间构建新的“空对象”,所以不再接着执行自身的构造函数

类初始化的过程

def __call__(obj_type, *args, **kwargs):
    obj = obj_type.__new__(*args, **kwargs)
    if obj is not None and issubclass(obj, obj_type):
        obj.__init__(*args, **kwargs)
	return obj

这一步的理解可以根据类的实例化步骤来理解。实例化由于()的缘故一定会调用模板类(正常类的模板类是type,模板类是区别父类的,对于父类来说是继承,但是对于模板类来说是实例,每一个正常的类都是type的实例,由type(calss)可以看出来)__call__,在这次调用的时候,完成了__new__分配内存空间,构建一个“空对象”,然后执行该类的构造函数。

在单例模式中,创建的Spam是类SingletonMetaclass的实例,也就是说它的模板类是SingletonMetaclass,在第一次Spam()时,调用的是SingletonMetaclass__call__,在其中又调用了SingletonMetaclass的模板类type__call__函数(也就是上面的函数这是一个简化版本),对应的程序语句是self.__instance = super().__call__(*args, **kwargs) = super().__call__(*args, **kwargs),这一句创建了对象之后并且完成了初始化。self是指向实例的,所以self.__instance就是Spam.__instance,它的值是构造的obj,当第二次使用Spam()时,进入模板类的__call__,此时做了判定self.__instance不是空的,所以没有进行正常的调用type.__call__()分配空间构建“空对象”完成调用自身构造函数的过程,直接将第一次的obj值赋予它,所以实现了单例,并且没有触发构造函数

该代码来自于python3-cookbook中元编程部分使用元类控制实例的创建,对于代码的理解来自于python设计模式-单例模式

元类

廖雪峰的教程对元类进行了解释:

metaclass直译为元类。正常我们定义类之后,可以根据这个类创建出实例:先定义类,然后创建实例。但是想创建出类的话必须根据metaclass创建出类:先定义metaclass,然后创建类,最后创建实例

默认习惯metaclass的类名总是以Metaclass结尾,以便清楚表示它是一个元类。在创建类的时候传入关键字metaclass

type的三种作用

1.派生出元类

元类的定义必须从type派生,正常的类若是没有继承使用的是object

class SingletonMetaclass(type):
    pass

2. 返回类型

type()可以查看一个类型或者变量的类型。对于基础数据类型的变量或者数据直接返回的就是基础数据类型的名称。对于类,输入类名得到的是type,对于实例化之后的变量得到的是<该类所在文件>.<类名>

class Hello(object):
    pass
type(Hello)#type
h = Hello()
type(h)#__main__.Hello,若是在其他文件定义的类,前面会变成文件名

3. 创建动态类

要创建一个class对象,type()函数依次传入3个参数:

  1. class的名称,字符串类型

  2. 继承的父类的集合,由于第二个参数属于元组类型,在单元素的时候(a)会表示歧义,此时()表示的是数学符号还是tuple是冲突 的,所以表示为(a,),加一个,消除歧义

  3. 属性的字典,dict类型,可以直接使用{'a':b}dict(a=b)的形式,传进去的属性可以是变量值,也可以是函数名

    注意在最后传入属性的时候,使用{}形式key要加’ ‘,使用dict()表达式形式不需要

    其中函数函数要提前创建,并且实例方法,类方法,静态方法还是按照如同在类内一样创建出来

#实例方法
def init(self):
    print('self:',self)
#类方法
@classmethod
def m(cls):
    return('cls',cls)
#静态方法
@staticmethod
def n():
    print('static')
# Test = type('Test',(object,),dict(init=init,m=m,n=n))
Test = type('Test',(object,),{'init':init,'m' : m,'n' : n})