【python高级】设计模式、类工厂、对象工厂

news2024/11/18 16:46:59

一、说明

        最近试着读Design pattern, 不过有些概念实在太抽象了, 整理一下自己所学抽象工厂的精神,就是要有abstract class(not implement),而所有不同种类的对象,都是继承这个abstract class,但是使用者只知道interface的接口就好。

二、关于不同类的使用案例

        很抽象吧,直接举例会比较实在,今天我开发了一个Qt windows app,Qt是跨平台的框架,所以Qt假设里面有个button class

class Button:
    def clicked():
        print(clicked)

        这个Button绝对不会这么简单,他还要跟不同操作系统兼容,所以就会有mac的button,windows的button,我们就可以先开个抽象类

class Button(ABC):
    @abstractmethod
    def clicked(self):
        pass
    class   WinButton(Button):
        def clicked(self):
            print('click winbutton')
    class MacButton(Button):
        def clicked(self): print('click macbutton')

        qt的interface可是非常多对象的,有button还有checkbox等等,这样我写程序的时候就要打self.btn = WinButton()这样子???,然后今天要打包到Mac上,又要改成self.btn = MacButton(),这样子是非常没有效率的...,能不能把这个作业系统的环境提取出来呢???

        白话就是,我们需要一个工厂,这个工厂可以帮我们呼叫下面不同的控件,例如WinFactory就是要给我windows的控件

class GUIFactory(ABC):
    @abstractmethod
    def create_button(self):
        pass
    @abstractmethod
    def create_checkbox(self):
        passclass WinFactory(GUIFactory):
    def create_button(self):
        return WinButton()
    def create_checkbox(self):
        return WinCheckBox() class MacFactory(GUIFactory):
    def create_button(self):
        return MacButton()
    def create_checkbox(self):
        return MacCheckBox()

        换个譬喻,你今天要吃大餐,而有前菜,主餐,甜点,而大餐有不同风格,你不会想要这样说: 主厨我要日式前菜,日式主餐,日式甜点。而是会想要这样说: 日式主厨,我要前菜、主餐、甜点。这边的日式主厨就是工厂啦!!主厨要会煮菜 上菜 切菜 等 这个就是抽象工厂,而日式主厨,西式主厨,就是工厂。

        那这样来看看前面的关系图怎么画

资料源: 抽象工厂设计模式

完整代码: 

from abc import ABC, abstractmethod
class GUIFactory(ABC):
    @abstractmethod
    def create_button(self):
        pass
    @abstractmethod
    def create_checkbox(self):
        pass
class WinFactory(GUIFactory):
    def create_button(self):
        return WinButton()
    def create_checkbox(self):
        return WinCheckBox()
class MacFactory(GUIFactory):
    def create_button(self):
        return MacButton()
    def create_checkbox(self):
        return MacCheckBox()
class Button(ABC):
    @abstractmethod
    def clicked(self):
        pass
    
class WinButton(Button):
    def clicked(self):
        print('click winbutton')
        
class MacButton(Button):
    def clicked(self):
        print('click macbutton')
class CheckBox(ABC):
    @abstractmethod
    def checked(self):
        pass
    
class WinCheckBox(CheckBox):
    def checked(self):
        print('check wincheckbox')
class MacCheckBox(CheckBox):
    def checked(self):
        print('check maccheckbox')
class Application:
    def __init__(self, factory):
        self.button = factory.create_button()
        self.checkbox = factory.create_checkbox()
    
if __name__ == "__main__":
    app = Application(MacFactory())
    app.button.clicked()

     注意,这样子,对用户而言,就是Application最后call的时候传入引数(Mac or Win Factory),这边甚至能加个if else直接判断当前os。最后那个对用户而言 看到的都是app
app.button, app.checkbox, 早就已经透过工厂创造好啦

三、总结:

        抽象工厂模式: 对用户来说单一接口,所有class需要继承abstract class。抽象工厂为你提供了一个接口, 可用于创建每个系列产品的对象。 只要代码通过该接口创建对象, 那么你就不会生成与应用程序已生成的产品类型不一致的产品。

设计模式

抽象工厂模式

四、什么是类工厂

2.1 类工厂定义

  • 类工厂本质

        类工厂就是一个在运行时创建类的函数。 即允许创建类时根据情况决定其属性,比如,根据用户输入创建属性。

  • 类工厂函数

        是一个用于创建并返回类的函数。

2.1 理解类工厂函数-函数返回一个“类”的变量

使用type创建类如下

def init(self, name):
    self.name = name
def eat(self):
    pass
def go_to_vet(self):
    print "go_to_vet"
return type('Animal', (object,), {
    '__doc__': 'A class representing an arbitrary animal.',
    '__init__': init,
    'eat': eat,
    'go_to_vet': go_to_vet,
})

这种方式的缺点:

  • 这种写法会将类Animal的函数置于和Animal同一层命名空间下,不利于层次化。因此一般不使用type之间创建。
  • 如果真需要使用type,则可以将其封装放入一个函数中。如下:

2.2 将类的“内脏”封装到函数中

下列示例表示

def create_animal_class():
    def init(self, name):
        self.name = name

    def eat(self):
        pass

    def go_to_vet(self):
        print(self.name)


    return type('Animal', (object,), {
        '__doc__': 'A class representing an arbitrary animal.',
        '__init__': init,
        'eat': eat,
        'go_to_vet': go_to_vet,
    })


Animal1 = create_animal_class( )
dog = Animal1("my dog")
dog.go_to_vet()
cat = Animal1("my cat")
cat.go_to_vet()

2.3  封装到函数内的class关键词

        通过函数调用即可获得一个自定义创建的Animal类。使用class关键字创建效果相同:

    def create_animal_class():
        class Animal(object):
	        def init(self, name):
	            self.name = name
	        def eat(self):
	            pass
	        def go_to_vet(self):
	            print "go_to_vet"
		return Animal
    Animal = create_animal_class()
    print Animal   #<class '__main__.Animal'>

五、编写类工厂的时机

  • 在需要基于运行时的信息(如用户输入)创建类时需要编写类工厂
    • 如果在编码时并不知道需要赋值给类的属性时
    • 类工厂示例:(创建类工厂的原因:假如说是为大量不同的第三方网站提供凭据的服务,则需要有多重不同的验证方式。该工厂可以根据数据库查询结果生成属性) 
#-*- coding:utf-8 -*-
def get_credential_class(use_proxy=False, tfa=False):
    if use_proxy:
        keys = ['service_name', 'email_address']  # 通过代理身份验证所要的密匙
    else:
        keys = ['username', 'password']
        if tfa:
            keys.append('tfa_token')


    class Credential(object):
        expected_keys = set(keys)

        def __init__(self, **kwargs):
            if self.expected_keys != set(kwargs.keys()):
                raise ValueError('Keys do not match')

            for k, v in kwargs.items():
                setattr(self, k, v)
    return Credential

cred = get_credential_class(0,0)
print(cred)

运行结果:
<class ‘main.Credential’>

  • 避免类属性一致性问题:处理类与实例之间属性不同的问题
class C(object):
    foo = 'bar'

class I(object):
    def __init__(self):
        self.foo = 'bar'
print C.foo()
print I.foo()   # AttributeError

c1 = C()   
c2 = C()  

c1.foo = 'baz'
print c1.foo   #baz
print c2.foo   # bar

C.foo = 'bacon'
print c1.foo   #baz
print c2.foo   #bacon

print c1.__dict__    # {'foo': 'baz'}
print c2.__dict__    #{}

 print I.foo() # AttributeError原因:
foo作为C的一个实例被实例化,但并不作为I的属性被实例化。
由于直接访问I而不是I的实例,因此__init__函数还没有被允许。
一个对象的属性查找顺序遵循首先查找实例对象自己,然后是类,接着是类的父类。
本质:__dict__属性存储着对象的所有属性(和值)
注意:一些内置的数据类型是没有__dict__属性的:
int,list,dict等这些常用的数据类型是没有__dict__属性的,其实这是可预料的,就算给了它们dict属性也没啥用,毕竟它们只是用来做数据容器的。

属性:指一个对象的数据或者函数
    - 属性的访问:通过句话(.)访问属性
    - 支持运行中添加和修改属性
字段:类的数据变量,例如:name='scolia'
方法:类里面的函数。可分为:
    - 实例方法:第一个参数需要是self,它表示一个具体的实例本身。
    - 类方法:用classmethod,它的第一个参数不是self,是cls,它表示这个类本身。类方法是那些并不需要类的实例就可以执行的方法
    - 静态方法:用staticmethod,可以无视self,而将这个方法当成一个普通的函数使用
 

#-*- coding:utf-8 -*-
class cls:
    clsvar = 1   #普通字段
    def __init__(self):
        self.insvar = 2

ins1 = cls()
ins2 = cls()

ins1.clsvar = 20
print cls.clsvar     #输出结果为1
print ins1.clsvar    #输出结果为20
print ins2.clsvar    #输出结果为1

#用类名为类变量重新赋值并打印
cls.clsvar = 10
print cls.clsvar     #输出结果为10
print ins1.clsvar    #输出结果为20
print ins2.clsvar    #输出结果为10

#这次直接给实例1没有在类中定义的变量赋值
ins1.x = 11
print ins1.x         #输出结果为11


#然后再用类名给类中没有定义的变量赋值
cls.m = 21
print cls.m          #输出结果为21

#再创建一个实例ins3,然后打印一下ins3的变量
ins3 = cls()
print ins3.insvar    #输出结果为2
print ins3.clsvar    #输出结果为10
print ins3.m         #输出结果为21
print ins3.x         #报错AttributeErro

六、类方法限制 

class C(object):
    foo = 'bar'

    @classmethod
    def classfoo(cls):
        return cls.foo

print c1.classfoo()  #bacon
print c2.classfoo()  #bacon

注意:类方法无法访问实例属性,它们并不需要一个实例,但需要类本身。因此c1.classfoo使用的是类的foo而不是实例c1的foo

6.1 使用类工厂

        使用时机:当你继承一个现有类并且所依赖的类属性必须调整时。类工厂是生成带有重载属性的恰当子类的一种恰当方式。

class C(object):
    foo = 'bar'

    @classmethod
    def classfoo(cls):
        return cls.foo

def create_C_subclass(new_foo):
    class SubC(C):
        foo = new_foo
    return SubC

S = create_C_subclass('spam')
print S.classfoo()  #spam
E = create_C_subclass('eggs')
print E.classfoo()  #eggs

执行C子类的classfoo类方法创建类的方式返回需要的结果。

6.2 单例模式

        让类工厂函数难以使用的一点是类工厂返回的是类而不是类的实例。如果一个实例,则必须调用类工厂函数返回的结果才可以。单例模式是一种只允许一个实例的类模式。
        类工厂示例:

class C(object):
    foo = 'bar'

    @classmethod
    def classfoo(cls):
        return cls.foo

def CPrime(new_foo='bar'):
    if new_foo == 'bar':
        return C()
    class SubC(C):
        foo = new_foo
    return SubC

EE = CPrime('bar')
FF = CPrime('bar1')
print EE  #<__main__.C object at 0x01777CB0>
print FF  #<class '__main__.SubC'>

 七、实例的工厂模式

        工厂模式是一个在软件开发中用来创建对象的设计模式。

        工厂模式包涵一个超类。这个超类提供一个抽象化的接口来创建一个特定类型的对象,而不是决定哪个对象可以被创建。

        为了实现此方法,需要创建一个工厂类并返回所需对象。

        当程序运行输入一个“类型”的时候,需要创建于此相应的对象。这就用到了工厂模式。在如此情形中,实现代码基于工厂模式,可以达到可扩展,可维护的代码。当增加一个新的类型,不在需要修改已存在的类,只增加能够产生新类型的子类。

        简短的说,当以下情形可以使用工厂模式:

  • 1.不知道用户想要创建什么样的对象
  • 2.当你想要创建一个可扩展的关联在创建类与支持创建对象的类之间。
"""
工厂模式:
根据需求产生对象。
"""
from typing import Any


class Clothes:
    """服装工厂类"""
    def __init__(self,name):
        self.name = name

    def create(self):
        pass

class Lovely(Clothes):

    def __init__(self, name):
        super().__init__(name)

    def create(self):
        print(f"{self.name} 生产 汉服")

class Cool(Clothes):

    def __init__(self, name):
        super().__init__(name)

    def create(self):
        print(f"{self.name} 生产 酷酷的、帅")

class Lipstick(Clothes):

    def __init__(self, name):
        super().__init__(name)

    def create(self):
        print(f"{self.name} 生产 死亡芭比粉")

class Shoes(Clothes):

    def __init__(self, name):
        super().__init__(name)

    def create(self):
        print(f"{self.name}生产 红色高跟鞋")

class Shoes2(Clothes):
    def __init__(self, name):
        super().__init__(name)

    def create(self):
        print(f"{self.name}生产 蓝色高跟鞋")

class Requirement:
    """定义一个需求类"""
    @staticmethod
    def creatNeed(need):
        if need == "死亡芭比粉":
            return Lipstick("死亡芭比粉")
        elif need == "酷酷的、帅":
            return Cool("酷酷的、帅")
        elif need == "汉服":
            return Lovely("汉服")
        elif need == "红色高跟鞋":
            return Shoes("红色高跟鞋")
        elif need == "蓝色高跟鞋":
            return Shoes2("蓝色高跟鞋")

# Requirement.creatNeed("死亡芭比粉").create()
# Requirement.creatNeed("红色高跟鞋").create()
# Requirement.creatNeed("汉服").create()

with open("create.txt","r",encoding="utf-8")as f_r:
    content = f_r.read()
    # print(content)
Requirement.creatNeed(content).create()

# class StudentNum:
#     num = 0
#
#     @classmethod
#     def add_num(cls):
#         cls.num += 1
#
#     @classmethod
#     def get_num(cls):
#         return cls.num
#
#     def __new__(cls) -> Any:
#         StudentNum.add_num()
#         return super().__new__(cls)
#
#
# class Student(StudentNum):
#     def __init__(self):
#         self.name = ""
# a = Student()
# b = Student()
# c = Student()
# print(StudentNum.get_num())

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1099530.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ubuntu 环境配置 Minecraft 基岩版服务器

文章目录 Part 1 搭建服务器Part 2 配置服务器Part 3 管理服务器一、手动备份服务器二、手动恢复服务器备份三、升级服务器 Part 1 搭建服务器 更新软件包信息 sudo apt-get update sudo apt-get upgrade安装所需工具 sudo apt-get vim sudo apt-get install zip sudo apt-g…

数字孪生在制造运行管理(MOM)的七大应用场景

数字经济时代&#xff0c;数字孪生作为实现各行各业智能化、数字化的重要手段之一&#xff0c;受到了各方的广泛重视。随着各项关键使能技术的不断发展&#xff0c;数字孪生的应用价值有望得到进一步释放。这些关键使能技术包括建模、渲染、仿真、物联网、虚拟调试、可视化等&a…

《机器人学导论》——探究未来世界的奇妙之旅

你是否感到生活变得越来越便利、智能&#xff1f;是的&#xff0c;这些都与机器人技术的发展密不可分。而想要更深入地了解机器人技术&#xff0c;一本好的书籍是必不可少的。那么&#xff0c;今天作者要向大家推荐的就是这样一本优秀的机器人学大作——《机器人学导论》。 《…

Flutter笔记:发布一个多功能轮播组件 awesome_carousel

Flutter笔记 电商中文货币显示插件 Money Display 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article/details/1338…

使用轮廓分数提升时间序列聚类的表现

我们将使用轮廓分数和一些距离指标来执行时间序列聚类实验&#xff0c;并且进行可视化 让我们看看下面的时间序列: 如果沿着y轴移动序列添加随机噪声&#xff0c;并随机化这些序列&#xff0c;那么它们几乎无法分辨&#xff0c;如下图所示-现在很难将时间序列列分组为簇: 上面…

SSM - Springboot - MyBatis-Plus 全栈体系(三十)

第七章 MyBatis-Plus MyBatis-Plus 高级用法&#xff1a;最优化持久层开发 一、MyBatis-Plus 快速入门 1. 简介 版本&#xff1a;3.5.3.1MyBatis-Plus (opens new window)&#xff08;简称 MP&#xff09;是一个 MyBatis (opens new window) 的增强工具&#xff0c;在 MyBa…

vscode终端显示多个虚拟环境

问题&#xff1a;某次突然发现vscode前面出现多个虚拟环境&#xff0c;即&#xff08;.conda&#xff09;&#xff08;base&#xff09;&#xff0c;其中&#xff08;base&#xff09;是默认自动激活的&#xff0c;但是&#xff08;.conda&#xff09;不是&#xff0c;而且我退…

上海亚商投顾:沪指震荡调整跌 减肥药、华为概念股持续活跃

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪 沪指上个交易日低开后震荡调整&#xff0c;深成指、创业板指盘中跌超1%&#xff0c;宁德时代一度跌超3%&#xff…

新增Node.js运行环境、新增系统缓存清理功能,1Panel开源面板v1.7.0发布

2023年10月16日&#xff0c;现代化、开源的Linux服务器运维管理面板1Panel正式发布v1.7.0版本。 在这个版本中&#xff0c;1Panel新增Node.js运行环境&#xff1b;新增系统缓存清理功能&#xff1b;应用安装时支持选择远程数据库。此外&#xff0c;我们进行了40多项功能更新和…

GitHub验证的2FA

一、 起因&#xff1a; GitHub需要双重身份验证 (2FA) 是登录网站或应用时使用的额外保护层。启用 2FA 时&#xff0c;必须使用您的用户名和密码登录&#xff0c;并提供另一种只有您知道或可以访问的身份验证形式。 二、解决&#xff1a; 2.1 这里使用chrome的身份验证插件进…

[Tkinter 教程08] Canvas 图形绘制

python - [译][Tkinter 教程08] Canvas 图形绘制 - 个人文章 - SegmentFault 思否 一、简介 Canvas 为 Tkinter 提供了绘图功能. 其提供的图形组件包括 线形, 圆形, 图片, 甚至其他控件. Canvas 控件为绘制图形图表, 编辑图形, 自定义控件提供了可能. 在第一个例子里, …

基于深度优先搜索的图遍历

这里写目录标题 基于深度优先搜索的无向图遍历算法流程图Python实现Java实现 基于深度优先搜索的有向图遍历Python实现 基于深度优先搜索的无向图遍历 使用深度优先搜索遍历无向图&#xff0c;将无向图用邻接表存储&#xff1a; 算法流程图 初始化起点 source&#xff0c;当…

2023_Spark_实验十四:SparkSQL入门操作

1、将emp.csv、dept.csv文件上传到分布式环境&#xff0c;再用 hdfs dfs -put dept.csv /input/ hdfs dfs -put emp.csv /input/ 将本地文件put到hdfs文件系统的input目录下 2、或者调用本地文件也可以。区别&#xff1a;sc.textFile("file:///D:\\temp\\emp.csv&qu…

苹果10月24日推送iOS 17.1:修复iPhone 12辐射超标问题 信号会更差

前段时间在iPhone 15系列发布的当天&#xff0c;法国突然宣布iPhone 12不能在该国销售&#xff0c;理由是iPhone 12超过了当地无线电频率暴露的法定范围。 根据法国监管机构ANFR(国家频率管理局)发布的最新消息&#xff0c;苹果将会在10月24日推送iOS 17.1正式版&#xff0c;届…

Prometheus的Pushgateway快速部署及使用

prometheus-pushgateway安装 一. Pushgateway简介 Pushgateway为Prometheus整体监控方案的功能组件之一&#xff0c;并做于一个独立的工具存在。它主要用于Prometheus无法直接拿到监控指标的场景&#xff0c;如监控源位于防火墙之后&#xff0c;Prometheus无法穿透防火墙&…

自动驾驶:控制算法概述

自动驾驶&#xff1a;控制算法概述 常见控制算法PID算法LQR算法MPC算法 自动驾驶控制算法横向控制纵向控制 参考文献 常见控制算法 PID算法 PID&#xff08;Proportional-Integral-Derivative&#xff09;控制是一种经典的反馈控制算法&#xff0c;通常用于稳定性和响应速度要…

MATLAB-文件自动批量读取文件,并按文件名称或时间顺序进行数据处理

我在处理文件数据时&#xff0c;发现一个一个文件处理效率太低&#xff0c;因此学习了下MATLAB中自动读取特定路径下文件信息的程序&#xff0c;并根据读取信息使用循环进行数据处理&#xff0c;提高效率&#xff0c;在此分享给大家这段代码并给予一些说明&#xff0c;希望能为…

.Net Core 6 运行环境手动安装流程

安装.NET Core 6 概述 在开始之前&#xff0c;我们首先需要了解一下整个安装过程的流程。下面的表格将展示安装.NET Core 6的步骤以及每一步需要做的事情。 步骤 动作 说明 1 下载.NET Core 6 SDK 从官方网站下载.NET Core 6 SDK安装包 2 安装.NET Core 6 SDK …

AXURE RP EXTENSION For Chrome 安装

在浏览器上输入地址&#xff1a;chrome://extensions/ 打开图片中这个选项&#xff0c;至此你就能通过index.html访问

【设计模式-1】UML和设计原则

说明&#xff1a;设计模式&#xff08;Design Pattern&#xff09;对于软件开发&#xff0c;简单来说&#xff0c;就是软件开发的套路&#xff0c;固定模板。在学习设计模式之前&#xff0c;需要首先学习UML&#xff08;Unified Modeling Language&#xff0c;统一建模语言&…