#模式的分类#
- (一)创建型模式;
- (二)结构型模式;
- (三)行为型模式。
模式的分类主要基于对象的创建方式、软件应用程序中类和对象的构造方式,同时还涉及对象之间的交互方式。
(一)创建型模式
以下是创建型模式的性质:
- 它们的运行机制基于对象的创建方式;
- 它们将对象的创建细节隔离开来;
- 代码与所创建的对象的类型无关。
单例模式是创建型模式的一个例子。
(二)结构型模式
以下是结构型模式的性质:
- 它们致力于设计出能够通过组合获得更强大功能的对象和类的结构;
- 重点是简化结构并识别类和对象之间的关系;
- 它们主要关注类的继承和组合。
适配器模式是结构型模式的一个例子。
(三)行为型模式
行为型模式具有下列性质:
- 它们关注对象之间的交互以及对象的响应性;
- 对象应该能够交互,同时任然保持松散耦合。
观察者模式是行为型模式的一个例子。
##一、单例设计模式
单例设计模式提供了这样一个机制,即确保类有且只有一个特定类型的对象,并提供全局访问点。因此,单例模式通常用于下列情形,例如日志记录或数据库操作、打印机后台处理程序,以及其他程序-----该程序运行过程中只能生成一个实例,以避免对同一资源产生相互冲突的请求。
简言之,单例设计模式的意图如下所示:
(1)确保有且只有一个对象被创建;
(2)为对象提供一个访问店,以使程序可以全局访问该对象;
(3)控制共享资源的并行访问。
实现单例模式的一个简单方法是:使构造函数私有化,并创建一个静态方法来完成对象的初始化。这样,对象将在第一次调用时创建,此后,这个类将返回同一个对象。
class Singleton:
def __new__(cls):
if not hasattr(cls, 'instance'):
cls.instance = super(Singleton, cls).__new__(cls)##???
return cls.instance
s = Singleton()
print('s created:', s)
s1 = Singleton()
print('s1 created:', s1)
在上面的代码中,我们通过覆盖__new__()方法(Python用于实例化对象的特殊方法)来控制对象的创建。对象s就是由__new__()方法创建的,但在创建之前,该方法会检查对象是否已存在。
方法hasattr(Python的特殊方法,用来了解对象是否具有某个属性)用于查看对象cls是否具有属性instance,该属性的作用是检查该类是否已经生成了一个对象。当对象s1被请求的时候,hasattr()发现对象已经存在,所以,对象s1将被分配已有的对象实例。
##二、单例模式中的懒汉式实例化
单例模式的用例之一就是懒汉式实例化。例如,在导入模块的时候,我们可能会无意中创建一个对象,但当时根本用不到它。懒汉式实例化能够确保在实际需要时才创建对象。所以,懒汉式实例化是一种节约资源并仅在需要时才创建它们的方式。
在下面的代码示例中,执行 s = Singleton() 的时候,它会调用__init__()方法,但没有新的对象被创建。然而,实际的对象创建发生在调用 Singleton.getInstance()的时候,我们正是通过这种方式来实现懒汉式实例化的。
class Singleton:
__instance = None
def __init__(self):
if not Singleton.__instance:
print('__init__ method called...')
else:
print('Instance already created:', self.getInstance())
@classmethod
def getInstance(cls):
if not cls.__instance:
cls.__instance = Singleton()
return cls.__instance
s = Singleton() #class initialized,but object not created
print('Object created:', Singleton.getInstance()) #Object gets created here
s1 = Singleton() #instance already created
##三、模块级别的单例模式
默认情况下,所有的模块都是单例,这是由Python的导入行为所决定的。
Python通过下列方式来工作:
- (1)检查一个Python模块是否已经导入;
- (2) 如果已经导入,则返回该模块的对象。如果还没有导入,则导入该模块,并实例化;
- (3)因此,当模块被导入的时候,它就会被初始化。然而,当同一个模块被再次导入的时候,它不会再次初始化,因为单例模式只能有一个对象,所以,它会返回同一个对象。
##四、Monostate单例模式
GoF的单例模式是指,一个类有且只有一个对象。然而,通常程序员需要的是让实例共享相同的状态。他建议开发人员应该关注状态和行为,而不是同一性。由于该概念基于所有对象共享相同状态,因此它也被成为Monostate(单态)模式。
Monostate模式可以通过Python轻松实现。在下面的代码中,我们将类变量"__shared_state"赋给了变量“dict”(它是Python的一个特殊变量)。Python使用“dict”存储一个类所有对象的状态。在下面的代码中,我们故意把"__shared_state"赋给所有已经创建的实例。所以,如果我们创建了两个实例b和b1,我们将得到两个不同的对象,这一点与单例模式大为不同,后者只能生成一个对象。然而,对象的状态,即“**b.dict **”和“b1.dict ”却是相同的。现在,就算对象b的对象变量x发生了变化,这个变化也会复制到被所有对象共享的“dict”变量,即b1的变量x的值也会从1变为4。
class Borg:
__shared_state = {'l': 2}
def __init__(self):
self.x = 1
self.__dict__ = self.__shared_state
pass
b = Borg()
b1 = Borg()
b.x = 4
print("Borg object b:", b)
print("Borg object b1:", b1)
print("Object state b:", b.__dict__)
print("Object state b1:", b1.__dict__)
除此之外,我们还可以通过修改“**new”方法本身来实现Borg模式。“new”方法是用来创建对象的实例的,具体如下所示:
class Borg:
_shared_state = {}
def __new__(cls, *args, **kwargs):
obj = super(Borg, cls).__new__(cls, *args, **kwargs)
obj.__dict__ = cls._shared_state
return obj
##五、单例和元类
元类是一个类的类,这意味着该类是它的元类的实例。使用元类,程序员有机会从预定义的Python类创建自己类型的类。例如,如果你有一个对象MyClass,你可以创建一个元类MyKls,它按照你需要的方式重新定义MyClass的行为。
在Python中,一切皆对象。如果我们说 a = 5,则 type(a) 返回<class ‘int’>,这意味着 a 是 int 类型。但是, type(int) 返回<type ‘type’>,这表明存在一个元类,因为 int 是 type 类型的类。
类的定义由它的元类决定,所以当我们用类A 创建一个类时,Python通过 A = MetaKls(name, bases, dict) 来创建类。
class MyInt(type):
def __call__(cls, *args, **kwargs):
print('***here is my int***', args)
print('Now do whatever you want with these objects...')
return type.__call__(cls, *args, **kwargs)
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”方法。)
以下示例代码能够更好地帮我们解释基于元类的单例实现:
class MetaSingleton(type):
_instance = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instance:
cls._instance[cls] = super(MetaSingleton, cls).__call__(*args, **kwargs)
return cls._instance[cls]
class Logger(metaclass = MetaSingleton):
pass
logger1 = Logger()
logger2 = Logger()
print(logger1, logger2)
##六、单例模式(一)
作为一个实际的用例,我们将通过一个数据库应用程序来展示单例的应用。不妨以需要对数据库进行多种读取和写入操作的云服务为例进行讲解。完整的云服务被分解为多个服务,每个服务执行不同的数据库操作。针对UI(Web应用程序)上的操作将导致调用API,最终产生相应的DB 操作。
很明显,跨不同服务的共享资源是数据库本身。因此,如果我们需要更好地设计云服务,必须主要以下几点:
- 数据库中操作的一致性,即一个操作不应与其他操作发送冲突。
- 优化数据库的各种操作,以提高内存和CPU的利用率。
Python实现:
import sqlite3
class MetaSingleton(type):
_instance = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instance:
cls._instance[cls] = super(MetaSingleton, cls).__call__(*args, **kwargs)
return cls._instance[cls]
class Database(metaclass = MetaSingleton):
connection = None
def connect(self):
if self.connection is None:
self.connection = sqlite3.connect('db.test')
self.cursorObj = self.connection.cursor()
return self.cursorObj
db1 = Database().connect()
db2 = Database().connect()
print('db1:', db1)
print('db2:', db2)
通过阅读上面代码,我们会发现以下几点:
- 我们以 MetaSingleton 为名创建了一个元类。Python的特殊方法"call*"可以通过元类创建实例。
- 数据库由MetaSingleton 类装饰后,其行为就会表现为单例。因此,当数据库被实例化时,它只创建一个对象。
- 当Web应用程序对数据库执行某些操作时,它会多次实例化数据库类,但只创建一个对象。因为只有一个对象,所以对数据库的调用是同步的。此外,这样还能节约系统资源,并且可以避免消耗过多的内存或CPU资源。
加入我们要开发的不是单个Web应用程序,而是集群化的情形,即多个Web应用共享单个数据库。当然,单例模式在这种情况下好像不太好使,因为每增加一个Web应用程序,就要创建一个单例,添加一个新的对象来查询数据库。这导致数据库操作无法同步,并且要耗费大量的资源。在这种情况下,数据库连接池比实现单例要好得多。
##七、单例模式(二)
另一种情况:为基础设施提供运行状况监控服务。
我们创建了HealthCheck类,它作为单例实现。我们还有维护一个被监控的服务器列表。当一个服务器从这个列表中
删除时,监控软件应该觉察到这一情况,并从被监控的服务器列表中将其删除。
在下面的代码中,hc1 和 hc2对象与单例中的类相同。
我们可以使用 addServer() 方法将服务器添加到基础设施中,以进行运行状况检查。首先,通过迭代对这些服务器
的运行状况进行检查。之后,changeServer() 方法会删除最后一个服务器,并向计划进行运行状况检查的基础设施中
添加一个新服务器。因此,当运行状况检查进行第二次迭代时,它会使用修改后的服务器列表。
所有这一切都可以借助单例模式来完成。当添加或删除服务器时,运行状况的检查工作必须由了解基础设施变动情况
的同一个对象来完成:
class HealthCheck:
_instance = None
def __new__(cls, *args, **kwargs):
if not HealthCheck._instance:
HealthCheck._instance = super(HealthCheck, cls).__new__(cls, *args, **kwargs)
return HealthCheck._instance
def __init__(self):
self._servers = []
def addServer(self):
self._servers.append('Server 1')
self._servers.append('Server 2')
self._servers.append('Server 3')
self._servers.append('Server 4')
def changeServer(self):
self._servers.pop()
self._servers.append('Server 5')
hc1 = HealthCheck()
hc2 = HealthCheck()
hc1.addServer()
print('Schedule check for servers (1)...')
for i in range(4):
print('Checking ', hc1._servers[i])
hc2.changeServer()
print('Schedule check for servers (2)...')
for i in range(4):
print('Checking ', hc2._servers[i])
##八、单例模式的缺点
由于单例具有全局访问权限,因此可能会出现以下问题:
(1)全局变量可能在某处已经被误改,但是开发人员任然认为它们没有发生变化,而该变量还在应用程序的其他位置被使用。
(2)可能会对同一对象创建多个引用。由于单例只创建一个对象,因此这种情况下会对同一个对象创建多个引用。
(3)所有依赖于全局变量的类都会由于一个类的改变而紧密耦合为全局数据,从而可能在无意中影响另一个类。