Devil May Code...

Vergil's Blog

Singleton(单例) —— 对象创建型模式

在GoF的《设计模式:可复用面向对象软件的基础》如下定义:

意图

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

适用性

在下面的情况下可以使用Singleton模式

  • 当类只能有一个实例切客户可以从一个众所周知的访问点访问它时。
  • 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。

参与者

Singleton —— 一定一个Instance操作,允许客户访问它的唯一实例。Instance是一个类操作(即Smalltalk中的一个类方法和C++中的一个静态成员函数)。

效果

  • 对唯一实例的受控访问
  • 缩小命名空间 Singleton模式是对全局变量的一种改进。它避免了那些存储唯一实例的全局变量污染命名空间
  • 允许对操作和表示的精化 Singleton类可以有子类。而且用这个扩展类的实例来配置一个应用是很容易的。你可以用你所需要的类的实例在运行时刻配置应用。
  • 允许可变数目的实例 这个模式使得你易于改变你的想法,并允许Singleton类的多个实例。此外,可以用相同的方法来控制应用所使用的实例的数目。只有允许访问Singleton实例的操作需要改变。
  • 比类操作更灵活

php实现

在一些语言(如C++,Java,php)实现单例模式基本上大同小异,私有构造方法使得无法在外部实例化单例对象,然后提供一个静态方法获得单例。以php为例:

<?php

class Singleton
{
    // 私有静态属性,用于保存实例
    private static $instance = null;
    
    //私有构造方法,不允许在外部使用
    private function __construct()
    {
        // ...
    }

    //提供一个访问它的静态方法获取实例
    public static function getInstance()
    {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    //不允许克隆对象
    private function __clone() {}

    //不允许serialize
    private function __sleep() {}

    //不允许unserialize
    private function __wakeup() {}
}

Python实现

然而python并没有真正的私有化支持。

__new__和 __init__

参考了python官方文档:https://docs.python.org/2/reference/datamodel.html#object.__new__

  • object.__new__(cls[, ...]):创建一个新的类实例cls时被调用。__new__()是一个静态方法。它将请求实例的类作为其第一个参数。其余的参数是传递给对象构造方法表达式(对类的调用)。__new__()的返回值应该是新的对象实例(通常是cls的一个实例)。
  • object.__init__(self[, ...]):在实例被创建(由__new__())之后调用,但在它返回给调用者之前。参数是传递给类构造函数表达式的参数。如果基类有__init__()方法,派生类的__init__()方法(如果有的话),必须明确地调用它来确保实例的基类部分的正确初始化。例如:BaseClass.__init__(self, [args...])
  • 因为__new__()__init__()共同创建对象。(__new__()用于创建,__init__()用于定制),__init__()不能返回非空值。这样做会导致在运行时引发TypeError。

使用__new__返回同一对象

class Singleton(object):

    __instance = None

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

测试:

a = Singleton()
b = Singleton()

print id(a) 
#4337124816
print id(b) 
#4337124816
print a is b 
#True

这看起来没什么问题。然而如上面所说__new__()__init__()共同创建对象。虽然重写了__new__()实现单例,但是__init__()同样会被调用:

class Singleton(object):

    __instance = None

    def __init__(self):
        print 'dosomething'

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

结果:

a = Singleton() 
#dosomething
b = Singleton() 
#dosomething

当然,可以稍作修改,不定义自动调用的__init__()方法,实例化后手动调用自己写的init()方法即可:

class Singleton(object):

    __instance = None

    def init(self):
        print 'dosomething'

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

使用装饰器

使用Python的装饰器可以轻松实现单例模式,而且非常自由,复用性高。

这个也是PEP-0318上的示例:

def singleton(cls):
    
    #定义一个字典局部变量
    instances = {}

    #闭包函数,可以使用这个局部变量instances
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    #返回闭包
    return getinstance

@singleton  #相当于singleton(MyClass)
class MyClass:
    def __init__(self):
        print 'dosomething'

测试:

a = MyClass()  
#dosomething 只输出一次
b = MyClass()
print id(a) 
#4337108536
print id(b) 
#4337108536
print a is b
#True

像“其他语言”一样的单例模式

就是如本文开头所说,像php,java之类的语言的单例模式。不能直接实例化对象,通过公开的静态方法获取对象唯一实例。

class Singleton:

    def __init__(self, decorated):
        self._decorated = decorated

    def Instance(self, *args, **kwargs):
        """
        返回单例实例。在它的第一次调用时,它创建一个装饰器类的新实例,并调用'__init__()'方法。
        在所有后续调用中,返回已创建的实例。

        """
        try:
            return self._instance
        except AttributeError:
            self._instance = self._decorated(*args, **kwargs)
            return self._instance
    
    # __call__()方法会在调用装饰器类时触发
    # 例如,如果有一个Bar类,并没有使用这个装饰器类。
    # 就会在调用 Singleton(Bar)() 时触发
    # 如果使用了这个装饰器,如Foo。相当于不能直接调用 Foo() 实例化对象。
    def __call__(self):
        raise TypeError('Singletons must be accessed through `Instance()`.')

    def __instancecheck__(self, inst):
        return isinstance(inst, self._decorated)

@Singleton   #相当于Singleton(Foo)
class Foo:
   def __init__(self):
       print 'Foo created'

调用代码:

f = Foo() 
# 错误。TypeError: Singletons must be accessed through `Instance()`.
f = Foo.Instance() 
g = Foo.Instance() 
print f is g 
# True

metaclass

以后补充

class Singleton(type):
    def __init__(cls, name, bases, dict):
        super(Singleton, cls).__init__(name, bases, dict)
        cls.instance = None 

    def __call__(cls,*args,**kw):
        if cls.instance is None:
            cls.instance = super(Singleton, cls).__call__(*args, **kw)
        return cls.instance

class MyClass(object):
    __metaclass__ = Singleton

Borg design pattern

这个并不算真正意义上的单例模式,只是让所有实例共享属性。它直接把__dict__指向同一个字典。

参考文章:http://code.activestate.com/recipes/66531/

class Borg:
    __shared_state = {}
    def __init__(self):
        self.__dict__ = self.__shared_state
    # and whatever else you want in your class -- that's all!

测试

a = MyClass()
b = MyClass()
print id(a)
#4337108464
print id(b)
#4337108536
print a is b
#False
a.name = 'Vergil'  #设置a的属性
print b.name       #打印b的属性
#Vergil
print id(a.__dict__)
#4337156456
print id(b.__dict__)
#4337156456
print a.__dict__ is b.__dict__
#True

参考文献

http://stackoverflow.com/questions/31875/is-there-a-simple-elegant-way-to-define-singletons/31887#31887

https://docs.python.org/2/reference/datamodel.html

http://code.activestate.com/recipes/66531/


添加新评论 »

在这里输入你的评论...

Powered by Typecho.