单例设计模式是应用开发过程中最简单和最著名的一种创建型设计模式。
实现单例模式的一个简单方法是,使构造函数私有化,并创建一个静态方法来完成对象的初始化。这样,对象将在第一次调用时创建,此后,这个类将返回同一个对象。
在使用 Python 的时候,我们的实现方式要有所变通,因为它无法创建私有的构造函数。下面,我们一起看看如何利用Python语言来实现单例模式。
class Singleton(object):
def __new__(cls, *args, **kwargs):
if not hasattr(cls, 'instance'):
cls.instance = super(Singleton, cls).__new__(cls)
return cls.instance
s = Singleton()
print("1-对象创建", s)
s2 = Singleton()
print("2-对象创建", s2)
在上面的代码中,我们通过覆盖__new__方法(python用来实例化对象的特殊方法)来控制对象的创建。对象s就是由__new__方法创建的。
方法hasattr用于查看对象是否具有属性instance,该属性的作用是检查该类是否已经生成了一个对象。
单例模式的用例之一就是懒汉式实例化。例如,在导入模块的时候,我们可能会无意中创建一个对象,但当时根本用不到它。懒汉式实例化能够确保在实际需要时才创建对象。所以,懒汉式实例化是一种节约资源并仅在需要时才创建它们的方式。
在下面的代码示例中,执行 s= singleton()的时候,它会调用 init 方法但没有新的对象被创建。然而,实际的对象创建发生在调用 singleton.getInstance()的时候,我们正是通过这种方式来实现懒汉式实例化的。
class Singleton:
_instance = None
def __init__(self):
if not Singleton._instance:
print("类已初始化,但实例未创建!")
else:
print("实例已创建", self.getInstance())
@classmethod
def getInstance(cls):
if not cls._instance:
cls._instance = Singleton()
return cls._instance
s = Singleton() # 类已初始化,但未创建对象
print("开始创建对象", Singleton.getInstance())
s1 = Singleton() # 此时对象已存在,不再创建新的
默认情况下,所有的模块都是单例,这是由 Python的导入行为所决定的
Python通过下列方式来工作。
在Monostate单例模式中,一个类有且只有一个对象,但与传统的单例模式不同的是,它关注的是实例的状态,而不是实例本身。因此,Monostate单例模式适合于需要让多个实例共享相同状态的情况。
Monostate单例模式的应用场景包括日志记录、数据库操作、打印机后台处理程序等,这些程序在运行过程中只能生成一个实例,以避免对同一资源产生相互冲突的请求。
class Borg:
__shared_state = {"x": "1"}
def __init__(self):
self.y = 2
# __dict__ python内置,用来存储一个类所有对象的状态
self.__dict__ = self.__shared_state
b = Borg()
b1 = Borg()
b.y = 4 # y的属性被所有对象共享
print(b)
print(b1) # 地址是不同的,
print(b.__dict__)
print(b1.__dict__) # 两个对象的状态是一致的!
元类是一个类的类,这意味着该类是它的元类的实例。使用元类,程序员有机会从预定义的 Python 类创建自己类型的类。
例如,如果你有一个对象Myclass,你可以创建一个元类MyKls,它按照你需要的方式重新定义Myclass 的行为。
在Python中,一切皆对象。如果我们说a=5,则type(a)返回
类的定义由它的元类决定,所以当我们用类A创建一个类时,Python 通过A=type(name,bases,dict)创建它。其中,name-类的名称; bases-基类; dict-属性变量;
现在,如果一个类有一个预定义的元类(名为 MyInt),那么 Python 就会通过A=MyInt(name,bases,dict)来创建类。
class MyInt(type):
def __call__(cls, *args, **kwds):
print("***** Here's My int *****args")
print("Now do whatever you want with these objects...")
return type.__call__(cls, *args, **kwds)
class int(metaclass=MyInt):
def __init__(self, x, y):
self.x = x
self.y = y
i = int(4, 5)
对于已经存在的类来说,当需要创建对象时,将调用 Python 的特殊方法__call__,在这段代码中,当我们使用int(4,5)实例化int 类时MyInt 元类的 call 方法将被调用,这意味着现在元类控制着对象的实例化。
前面的思路同样适用于单例设计模式。由于元类对类创建和对象实例化有更多的控制权,所以它可以用于创建单例。(注意:为了控制类的创建和初始化,元类将覆盖 __new__和 init 方法。)
class MetaSingleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(MetaSingleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Logger(metaclass=MetaSingleton):
pass
logger1 = Logger()
logger2 = Logger()
print(logger1, logger2)
import sqlite3
class MetaSingleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(MetaSingleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Database(metaclass=MetaSingleton):
connection = None
def connect(self):
if self.connection is None:
self.connection = sqlite3.connect("db.sqlite3")
self.cursorobj = self.connection.cursor()
return self.cursorobj
db1 = Database().connect()
db2 = Database().connect()
print("Database Objects DB1", db1)
print("Database Objects DB2", db2) # 可以看出两个结果是一致的
虽然单例模式在许多情况下效果很好,但这种模式仍然存在一些缺陷。由于单例具有全局访问权限,因此可能会出现以下问题
● 全局变量可能在某处已经被误改,但是开发人员仍然认为它们没有发生变化,而该变量还在应用程序的其他位置被使用。
● 可能会对同一对象创建多个引用。由于单例只创建一个对象,因此这种情况下会对同一个对象创建多个引用。
● 所有依赖于全局变量的类都会由于一个类的改变而紧密耦合为全局数据,从而可能在无意中影响另一个类。