python设计模式笔记(一):单例和工厂

  • 引言
  • 创建型模式
    • 单例模式
      • 使用模块
      • 使用__new__方法
      • 使用装饰器
      • 单例进阶
      • 单例优缺点
    • 工厂模式
      • 简单工厂模式
      • 工厂方法模式
      • 抽象工厂模式
      • 工厂方法的应用
      • 工厂方法的优缺点
  • 总结

引言

什么是设计模式?在我目前看来,设计模式是一种经过前人总结和优化的理念与方案,在某些特定的情况下,它算得上是足够高级的能解决我们当前的错误,以及代码的冗余,但在带给我们思路的同时,如果我们利用不当,用在了某些不正确的场景,那么也会造成不必要的损失,为日后留下伏笔。所以,我便想以系列的形式,与现状手头上的资料,去整合一下设计模式的笔记,可能之前有些或多或少用过的,但并没有当回事,所以在这里,我便想通过4-6篇博文,加深一下对设计模式的理解,同时也是学习这个新体系了。

创建型模式

  1. 创建模式,提供实例化的方法,为适合的状况提供相应的对象创建方法。
  2. 结构化模式,通常用来处理实体之间的关系,使得这些实体能够更好地协同工作。
  3. 行为模式,用于在不同的实体建进行通信,为实体之间的通信提供更容易,更灵活的通信方法。

按照以上较笼统的介绍,我们可以将设计模式暂分为三种模式,如果想看概括比较全面的解释可以看下图,但出处我暂时也没找到。而我们本篇想要介绍的,便是创建模式,而其中它又可以分为单例模式、工厂模式。

python设计模式笔记(一):单例和工厂_第1张图片

单例模式

什么是单例模式?单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。

在 Python 中,我们可以用多种方法来实现单例模式:

  • 使用模块
  • 使用 __new __
  • 使用装饰器(decorator)
  • 使用元类(metaclass)

我们主要介绍前面三种。

使用模块

其实,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的缓存文件。
python设计模式笔记(一):单例和工厂_第2张图片

使用__new__方法

__ 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与设计模式–单例模式

单例模式的优点:

  1. 由于单例模式要求在全局内只有一个实例,因而可以节省比较多的内存空间;
  2. 全局只有一个接入点,可以更好地进行数据同步控制,避免多重占用;
  3. 单例可长驻内存,减少系统开销。

单例模式的应用举例:

  1. 生成全局惟一的序列号;
  2. 访问全局复用的惟一资源,如磁盘、总线等;
  3. 单个对象占用的资源过多,如数据库等;
  4. 系统全局统一管理,如Windows下的Task Manager;
  5. 网站计数器。

单例模式的缺点:

  1. 单例模式的扩展是比较困难的;
  2. 赋于了单例以太多的职责,某种程度上违反单一职责原则(六大原则后面会讲到);
  3. 单例模式是并发协作软件模块中需要最先完成的,因而其不利于测试;
  4. 单例模式在某种情况下会导致“资源瓶颈”。

工厂模式

工厂模式是一种创建型的设计模式,作用如其名称:这是一个就像工厂那样生产对象实例的类。

工厂模式的定义如下:定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。其通用类图如下。其产品类定义产品的公共属性和接口,工厂类定义产品实例化的“方式”。

python设计模式笔记(一):单例和工厂_第3张图片
python设计模式笔记(一):单例和工厂_第4张图片

工厂模式主要有两种形式:

第一,工厂方法,它是根据不同的输入返回不同对象的方法;

第二,抽象工厂,它是创建系列相关对象的方法组。

简单工厂模式

什么是简单工厂模式?就是我们像工厂传入我们需要创建的产品类型,然后返回相应的产品。

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与设计模式–工厂类相关模式

工厂模式、抽象工厂模式的优点:

  1. 工厂模式巨有非常好的封装性,代码结构清晰;在抽象工厂模式中,其结构还可以随着需要进行更深或者更浅的抽象层级调整,非常灵活;
  2. 屏蔽产品类,使产品的被使用业务场景和产品的功能细节可以分而开发进行,是比较典型的解耦框架。

工厂模式、抽象工厂模式的使用场景:
当系统实例要求比较灵活和可扩展时,可以考虑工厂模式或者抽象工厂模式实现。比如,在通信系统中,高层通信协议会很多样化,同时,上层协议依赖于下层协议,那么就可以对应建立对应层级的抽象工厂,根据不同的“产品需求”去生产定制的实例。

工厂类模式的不足:

  1. 工厂模式相对于直接生成实例过程要复杂一些,所以,在小项目中,可以不使用工厂模式;
  2. 抽象工厂模式中,产品类的扩展比较麻烦。毕竟,每一个工厂对应每一类产品,产品扩展,就意味着相应的抽象工厂也要扩展。

总结

感觉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版设计模式实践

你可能感兴趣的:(python)