草庐IT

Python设计模式-创建型:单例模式和工厂模式家族

OCEANEYES.GZY 2023-04-15 原文

Python设计模式-创建型:单例模式和工厂模式家族

知识点

  • 单例模式概念及一般实现
  • 单例模式的装饰器实现
  • 简单工厂模式
  • 抽象工厂模式

单例模式(singleton)

  • 所谓单例模式,也就是说不管什么时候我们要确保只有一个对象实例存在。
  • 很多情况下,整个系统中只需要存在一个对象,所有的信息都从这个对象获取,比如系统的配置对象,或者是线程池。
  • 这些场景下,就非常适合使用单例模式。总结起来,就是说不管我们初始化一个对象多少次,真正干活的对象只会生成一次并且在首次生成。

Singleton._instance

# -*- 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())


  • 使用类变量 Singleton._instance 来存储创建的实例,并且保证只会创建一次实例。

  • 由于 Python 是一门动态语言,我们可以在运行时改变类定义。

  • 在首次初始化Singleton时,我们将首次生成类_A的实例,并将其存储到 Singleton._instance 中,以后每次初始化 Singleton 时都从 Singleton._instance 获取真正干活的实例,这样我们就实现了单例模式。

装饰器

# -*- coding: utf-8 -*-

class Singleton:

    """
    单例类装饰器,可以用于想实现单例的任何类。注意,不能用于多线程环境。
    """

    def __init__(self, cls):
        """ 需要的参数是一个类 """
        self._cls = cls

    def Instance(self):
        """
        返回真正的实例
        """
        try:
            return self._instance
        except AttributeError:
            self._instance = self._cls()
            return self._instance

    def __call__(self):
        raise TypeError('Singletons must be accessed through `Instance()`.')

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


# 装饰器
@Singleton
class A:
    """一个需要单例模式的类"""
    def __init__(self):
        pass

    def display(self):
        return id(self)

if __name__ == '__main__':
    s1 = A.Instance()
    s2 = A.Instance()
    print(s1, s1.display())
    print(s2, s2.display())
    print(s1 is s2)

  • 用装饰器实现了单例模式,任何想使用单例模式的类,只需要使用 Singleton 装饰器装饰一下就可以使用了。
  • 可以看到其核心工作原理其实和第一种实现方式是一致的,也是使用内置的属性 Singleton._instance 来存储实例的。通过使用装饰器的模式我们将代码解耦了,使用更加灵活

案例-单例模式-连接sqlite3数据库

# -*- coding: utf-8 -*-


import sqlite3
from flask import current_app
from flask import _app_ctx_stack as stack


class SQLite3(object):

    def __init__(self, app=None):
        self.app = app
        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        """
        典型的 Flask 扩展的初始化方式
        """
        app.config.setdefault('SQLITE3_DATABASE', ':memory:')
        app.teardown_appcontext(self.teardown)

    def connect(self):
        """
        连接到 sqlite 数据库
        """
        return sqlite3.connect(current_app.config['SQLITE3_DATABASE'])

    def teardown(self, exception):
          """
          关闭 sqlite 链接
          """
        ctx = stack.top
        if hasattr(ctx, 'sqlite3_db'):
            ctx.sqlite3_db.close()

    @property
    def connection(self):
        """
        单例模式在这里:使用 flask._app_ctx_stack 存放 sqlite 链接,
        每次获取数据库链接时都通过 connection 获取
        """
        ctx = stack.top
        if ctx is not None:
            if not hasattr(ctx, 'sqlite3_db'):
                ctx.sqlite3_db = self.connect()
            return ctx.sqlite3_db

简单工厂模式 Simple factory

一个函数,传入需要创建的产品类型,然后返回相应的产品

# -*- coding: utf-8 -*-


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())

工厂方法模式 factory method

上面的简单工厂模式中,我们遇到了问题:如果需要增加一种课程,那我们需要修改工厂代码。仔细想想,如果对工厂进行抽象化,让每个工厂只负责一种产品的生产,那这样当增加一种产品时,就不需要修改已有的工厂了,只需要新增加一个工厂就行了,这样就避免修改整个工厂了

# -*- coding: utf-8 -*-

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模块实现抽象类和抽象方法。

抽象工厂模式

在工厂方法模式中,我们会遇到一个问题,当产品非常多时,继续使用工厂方法模式会产生非常多的工厂类。

如果按照工厂方法模式的作法,我们需要创建 Linux 虚拟机工厂类和 Mac 虚拟机工厂类, 这样我们就会有一堆工厂类了。我们就不能创建出一个能同时创建课程和虚拟机的工厂吗?

-*- coding: utf-8 -*-

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 LinuxVm(object):
    """
    Linux 虚拟机
    """

    def start(self):
        return "Linux vm running"


class MacVm(object):
    """
    Mac OSX 虚拟机
    """

    def start(self):
        return "Mac OSX vm running"


class Factory(metaclass=abc.ABCMeta):
    """
    抽象工厂类, 现在工厂类不仅能创建课程,还能创建虚拟机了
    """

    @abc.abstractmethod
    def create_course(self):
        pass

    @abc.abstractmethod
    def create_vm(self):
        pass


class BasicCourseLinuxFactory(Factory):
    """
    基础课程工厂类
    """

    def create_course(self):
        return BasicCourse()

    def create_vm(self):
        return LinuxVm()


class ProjectCourseMacFactory(Factory):
    """
    项目课程工厂类
    """

    def create_course(self):
        return ProjectCourse()

    def create_vm(self):
        return MacVm()


def get_factory():
    """
    随机获取一个工厂类
    """
    return random.choice([BasicCourseLinuxFactory, ProjectCourseMacFactory])()


if __name__ == '__main__':
    factory = get_factory()
    course = factory.create_course()
    vm = factory.create_vm()
    print(course.get_labs())
    print(vm.start())

有关Python设计模式-创建型:单例模式和工厂模式家族的更多相关文章

  1. ruby - 如何在 Ruby 中顺序创建 PI - 2

    出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits

  2. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  3. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  4. ruby-on-rails - Railstutorial : db:populate vs. 工厂女孩 - 2

    在railstutorial中,作者为什么选择使用这个(代码list10.25):http://ruby.railstutorial.org/chapters/updating-showing-and-deleting-usersnamespace:dbdodesc"Filldatabasewithsampledata"task:populate=>:environmentdoRake::Task['db:reset'].invokeUser.create!(:name=>"ExampleUser",:email=>"example@railstutorial.org",:passwo

  5. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  6. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  7. ruby-on-rails - 无法使用 Rails 3.2 创建插件? - 2

    我对最新版本的Rails有疑问。我创建了一个新应用程序(railsnewMyProject),但我没有脚本/生成,只有脚本/rails,当我输入ruby./script/railsgeneratepluginmy_plugin"Couldnotfindgeneratorplugin.".你知道如何生成插件模板吗?没有这个命令可以创建插件吗?PS:我正在使用Rails3.2.1和ruby​​1.8.7[universal-darwin11.0] 最佳答案 随着Rails3.2.0的发布,插件生成器已经被移除。查看变更日志here.现在

  8. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  9. ruby - 如何使用 RSpec::Core::RakeTask 创建 RSpec Rake 任务? - 2

    如何使用RSpec::Core::RakeTask初始化RSpecRake任务?require'rspec/core/rake_task'RSpec::Core::RakeTask.newdo|t|#whatdoIputinhere?endInitialize函数记录在http://rubydoc.info/github/rspec/rspec-core/RSpec/Core/RakeTask#initialize-instance_method没有很好的记录;它只是说:-(RakeTask)initialize(*args,&task_block)AnewinstanceofRake

  10. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

随机推荐