什么是设计模式?在我目前看来,设计模式是一种经过前人总结和优化的理念与方案,在某些特定的情况下,它算得上是足够高级的能解决我们当前的错误,以及代码的冗余,但在带给我们思路的同时,如果我们利用不当,用在了某些不正确的场景,那么也会造成不必要的损失,为日后留下伏笔。所以,我便想以系列的形式,与现状手头上的资料,去整合一下设计模式的笔记,可能之前有些或多或少用过的,但并没有当回事,所以在这里,我便想通过4-6篇博文,加深一下对设计模式的理解,同时也是学习这个新体系了。
- 创建模式,提供实例化的方法,为适合的状况提供相应的对象创建方法。
- 结构化模式,通常用来处理实体之间的关系,使得这些实体能够更好地协同工作。
- 行为模式,用于在不同的实体建进行通信,为实体之间的通信提供更容易,更灵活的通信方法。
按照以上较笼统的介绍,我们可以将设计模式暂分为三种模式,如果想看概括比较全面的解释可以看下图,但出处我暂时也没找到。而我们本篇想要介绍的,便是创建模式,而其中它又可以分为单例模式、工厂模式。
什么是单例模式?单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。
在 Python 中,我们可以用多种方法来实现单例模式:
我们主要介绍前面三种。
其实,Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。我们来看一个例子,首先写一个py文件,比如说:
class My_Singleton(object):
def func(self):
print("hello world!")
my_singleton = My_Singleton()
然后再创建一个py文件去调用它,比如我创建了一个333.py文件,然后导入该模块进行调用:
from mysingleton import my_singleton
my_singleton.func()
然后我们就可以在控制台看到“hello world!”,在该项目文件夹下,看到.pyc的缓存文件。
__ new__() 是一个静态方法,该方法将实例被请求的类作为第一个参数,将类调用时传递的参数作为剩下的参数。__ new__() 的返回值应该是新的对象实例。(通常是类cls的实例)。而它与__ init__()方法不同之处在于__init__()方法在__new__()方法创建实例后,实例被返回给调用者之前调用。它的参数是传递给构造表达式的那些参数。也就是说__new__()用于创建实例,而__init__()则负责初始化实例。所以new方法可以用来构造单例。
class Singleton(object):
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance: # 反反为真
cls._instance = super(Singleton, cls).__new__(cls)
return cls._instance
class MyClass(Singleton):
a = 1
one = MyClass()
two = MyClass()
print(one == two) # 做一个Boolean判断
print(id(one),id(two)) # 返回对象的唯一标识,在Cpython中可以认为是对象引用的内存地址
print(one,two)
"""
True
2029424727656 2029424727656
<__main__.MyClass object at 0x000001D88323D668> <__main__.MyClass object at 0x000001D88323D668>
"""
上面这段代码我觉得是最为能概括单例特点的一段,首先来看Singleton类,我们将类的实例和一个类变量 _instance 关联起来,如果 cls._instance 为 None 则创建实例,否则直接返回 cls._instance。这体现了单例的一个特点,即全局赋予一个实例,而后面的MyClass类则是继承了Singleton,从打印结果可以看出,one和two对象同样的内存地址也能说明他们共享了属性。下面让我们再来看另一个例子:
"""
选自:http://code.activestate.com/recipes/66531/
"""
class Singleton(object):
_state = {}
def __new__(cls, *args, **kw):
ob = super(Singleton, cls).__new__(cls, *args, **kw)
ob.__dict__ = cls._state
return ob
class MyClass2(Singleton):
a = 1
one = MyClass2()
two = MyClass2()
# one和two是两个不同的对象,id, ==, is对比结果可看出
two.a = 3
print(one.a)
# 3
print(id(one))
# 2981458138784
print(id(two))
# 2981458138000
print(one == two)
# False
print(one is two)
# False
# 但是one和two具有相同的(同一个__dict__属性),见:
print(id(one.__dict__))
# 2981458090240
print(id(two.__dict__))
# 2981458090240
我们可以看到上面这个例子,one和two这里虽然是两个不同的对象,但他们引用了同一个类的实例,即在该单例模式下,他们都共享了相同的属性__dict__,将所有实例的__dict__指向同一个字典,这样实例就共享相同的方法和属性。对任何实例的名字属性的设置,无论是在__init__中修改还是直接修改,所有的实例都会受到影响。不过实例的id是不同的。要保证类实例能共享属性,但不和子类共享,注意使用cls._state,而不是Singleton._state。
什么是装饰器?从概念来讲,就是在不改变原有代码的同时,动态地修改一个类或函数的功能。装饰器也可以看做是一种模式,并且和六大原则中的开放封闭原则有很大关系,这个我们以后再讲,顺便考虑另开一篇讲装饰器的博文,这里我们可以先从闭包讲起:
闭包(closure)是函数式编程的重要的语法结构。它需要具有如下几个特点:
然后我们可以根据如上提示,大概写出闭包的一个通用式:
def outer(func):
def inner(*arg,**kwargs):
ret = func(*arg,**kwargs)
return ret
return inner # 返回的 inner,inner代表的是函数,非执行函数
然后我们就可以写出闭包结构下的单例模式:
from functools import wraps
def singleton(cls):
instances = {}
@wraps(cls) # 这是一个python内置的装饰器工具,装饰器修复技术,目的是让被装饰的函数的属性不会被改变
def getinstance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return getinstance
@singleton
class MyClass3(object):
a = 1
one = MyClass3()
two = MyClass3()
three = MyClass3()
print(id(one),id(two),id(three))
"""
2710090207976 2710090207976 2710090207976
"""
在上面,我们定义了一个装饰器 singleton,它返回了一个内部函数 getinstance,该函数会判断某个类是否在字典 instances 中,如果不存在,则会将 cls 作为 key,cls(*args, **kw) 作为 value 存到 instances 中,否则,直接返回 instances[cls]。
我们再来看看在完整的类中单例是如何运作的:
# -*- coding: utf-8 -*-
class Singleton(object):
"""
单例模式
"""
class _A(object):
"""
真正干活的类, 对外隐藏
"""
def __init__(self):
pass
def display(self):
""" 返回当前实例的 ID,是全局唯一的"""
return id(self)
# 类变量,用于存储 _A 的实例
_instance = None
def __init__(self):
""" 先判断类变量中是否已经保存了 _A 的实例,如果没有则创建一个后返回"""
if Singleton._instance is None:
Singleton._instance = Singleton._A()
def __getattr__(self, attr):
""" 所有的属性都应该直接从 Singleton._instance 获取"""
return getattr(self._instance, attr)
if __name__ == '__main__':
# 创建两个实例
s1 = Singleton()
s2 = Singleton()
print(id(s1), s1.display())
print(id(s2), s2.display())
然后单例模式是有非常多的应用场景的,就我目前印象最深的一个demo,以前写过的一个手机发短信的单例:
class CCP(object):
"""发送短信的辅助类"""
def __new__(cls, *args, **kwargs):
# 判断是否存在类属性_instance,_instance是类CCP的唯一对象,即单例
if not hasattr(CCP, "_instance"):
# 将耗时的网络请求封装到单例中,只会第一次初始化对象的时候才来验证,后面就不会来了
# 调用父类的__new__创建对象
cls._instance = super(CCP, cls).__new__(cls, *args, **kwargs)
# 用户鉴权处理(网络请求消耗性能)
cls._instance.rest = REST(_serverIP, _serverPort, _softVersion)
cls._instance.rest.setAccount(_accountSid, _accountToken)
cls._instance.rest.setAppId(_appId)
# 返回对象
return cls._instance
def send_template_sms(self, to, datas, temp_id):
"""发送模板短信"""
result = self.rest.sendTemplateSMS(to, datas, temp_id)
print(result)
# 如果云通讯发送短信成功,返回的字典数据result中statuCode字段的值为"000000"
if result.get("statusCode") == "000000":
# 返回0 表示发送短信成功 再次封装
return 0
else:
# 返回-1 表示发送失败
return -1
第一次需要通过if判断,进行权限检测,第二次就不再需要检测了,会直接返回。
此部分可以参考链接Python与设计模式–单例模式
单例模式的优点:
单例模式的应用举例:
单例模式的缺点:
工厂模式是一种创建型的设计模式,作用如其名称:这是一个就像工厂那样生产对象实例的类。
工厂模式的定义如下:定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。其通用类图如下。其产品类定义产品的公共属性和接口,工厂类定义产品实例化的“方式”。
工厂模式主要有两种形式:
第一,工厂方法,它是根据不同的输入返回不同对象的方法;
第二,抽象工厂,它是创建系列相关对象的方法组。
什么是简单工厂模式?就是我们像工厂传入我们需要创建的产品类型,然后返回相应的产品。
import random
class BasicCourse(object):
"""
基础课程
"""
def get_labs(self):
return "basic_course: labs"
def __str__(self):
return "BasciCourse"
class ProjectCourse(object):
"""
项目课
"""
def get_labs(self):
return "project_course: labs"
def __str__(self):
return "ProjectCourse"
class SimpleCourseFactory(object):
@staticmethod
def create_course(type):
""" 简单工厂,用于创建课程"""
if type == 'bc':
return BasicCourse()
elif type == 'pc':
return ProjectCourse()
if __name__ == '__main__':
t = random.choice(['bc', 'pc'])
course = SimpleCourseFactory.create_course(t)
print(course.get_labs())
import random
import abc
class BasicCourse(object):
"""
基础课程
"""
def get_labs(self):
return "basic_course: labs"
def __str__(self):
return "BasicCourse"
class ProjectCourse(object):
"""
项目课
"""
def get_labs(self):
return "project_course: labs"
def __str__(self):
return "ProjectCourse"
class Factory(metaclass=abc.ABCMeta):
"""
抽象工厂类
"""
@abc.abstractmethod
def create_course(self):
pass
class BasicCourseFactory(Factory):
"""
基础课程工厂类
"""
def create_course(self):
return BasicCourse()
class ProjectCourseFactory(Factory):
"""
项目课程工厂类
"""
def create_course(self):
return ProjectCourse()
def get_factory():
"""
随机获取一个工厂类
"""
return random.choice([BasicCourseFactory, ProjectCourseFactory])()
if __name__ == '__main__':
factory = get_factory()
course = factory.create_course()
print(course.get_labs())
以上代码中,我们有两种课程:BasicCourse 和 ProjectCourse,分别对应基础课和项目课。接着,我们创建了一个抽象的工厂 Factory,该工厂有一抽象方法Factory.create_course用于创建课程,最后我们基于抽象工厂实现了生产基础课程的工厂BasicCourseFactory和生产项目课的工厂ProjectCourseFactory。这样当我们新增加一种课程时,就不需要修改已经存在的基础课工厂和项目课工厂了。这里需要说明下,我们通过 Python 的abc模块实现抽象类和抽象方法。
未完待续
未完待续
此部分可以参考链接Python与设计模式–工厂类相关模式
工厂模式、抽象工厂模式的优点:
工厂模式、抽象工厂模式的使用场景:
当系统实例要求比较灵活和可扩展时,可以考虑工厂模式或者抽象工厂模式实现。比如,在通信系统中,高层通信协议会很多样化,同时,上层协议依赖于下层协议,那么就可以对应建立对应层级的抽象工厂,根据不同的“产品需求”去生产定制的实例。
工厂类模式的不足:
感觉Python的设计模式还有许多东西可以深挖,本篇博客只是列举了创建者模式下的单例和工厂模式,还有建造者模式没有提及,我也还没开始看,不太记得之前是否用过了。写到这里,我感觉设计模式还是挺有意思的,不论是Java、C++还是Python,设计模式都具有很强的通用性,等过一段时间我再开始第二篇的写作。
参考与推荐:
[1]. 《Mastering Python Design Patterns》
[2]. Python系列干货之——Python与设计模式
[3]. 开放封闭原则
[4]. Python单例模式的4种实现方法
[5]. https://en.wikipedia.org/wiki/Software_design_pattern
[6]. 《Java设计模式之抽象工厂模式》
[7]. 实验楼之Python版设计模式实践