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

一、说明

        最近试着读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):@abstractmethoddef clicked(self):passclass   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):@abstractmethoddef create_button(self):pass@abstractmethoddef 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):@abstractmethoddef create_button(self):pass@abstractmethoddef 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):@abstractmethoddef clicked(self):passclass WinButton(Button):def clicked(self):print('click winbutton')class MacButton(Button):def clicked(self):print('click macbutton')
class CheckBox(ABC):@abstractmethoddef checked(self):passclass 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 = namedef eat(self):passdef 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 = namedef eat(self):passdef go_to_vet(self):print "go_to_vet"return AnimalAnimal = 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 Credentialcred = 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()   # AttributeErrorc1 = C()   
c2 = C()  c1.foo = 'baz'
print c1.foo   #baz
print c2.foo   # barC.foo = 'bacon'
print c1.foo   #baz
print c2.foo   #baconprint 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 = 2ins1 = 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'@classmethoddef classfoo(cls):return cls.fooprint c1.classfoo()  #bacon
print c2.classfoo()  #bacon

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

6.1 使用类工厂

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

class C(object):foo = 'bar'@classmethoddef classfoo(cls):return cls.foodef create_C_subclass(new_foo):class SubC(C):foo = new_fooreturn SubCS = 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'@classmethoddef classfoo(cls):return cls.foodef CPrime(new_foo='bar'):if new_foo == 'bar':return C()class SubC(C):foo = new_fooreturn SubCEE = CPrime('bar')
FF = CPrime('bar1')
print EE  #<__main__.C object at 0x01777CB0>
print FF  #<class '__main__.SubC'>

 七、实例的工厂模式

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

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

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

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

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

  • 1.不知道用户想要创建什么样的对象
  • 2.当你想要创建一个可扩展的关联在创建类与支持创建对象的类之间。
"""
工厂模式:
根据需求产生对象。
"""
from typing import Anyclass Clothes:"""服装工厂类"""def __init__(self,name):self.name = namedef create(self):passclass 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:"""定义一个需求类"""@staticmethoddef 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.rhkb.cn/news/160034.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

unordered_set unordered_map 的封装

目录 1. 哈希的概念 1.1. 哈希冲突 1.2. 哈希函数&#xff1a; 1. 直接定址法 2. 除留余数法 1.3. 闭散列实现哈希 1.4. 开散列实现哈希 2. 哈希的应用 2.1 位图的概念 2.1.1. 问题&#xff1a; 2.2.1. set ​编辑 2.2.2. reset 2.2.3. test() 2.2. 位图的实现…

使用解构赋值简化axios返回对象属性元素的提取

axios返回的response通常都会进行一层封装&#xff0c;把响应的数据封装到了data这个对象&#xff0c;所以提取数据起来不太方便&#xff0c;往往需要res.data.xxx这样获取里面的数据&#xff0c; 具体可以参考下面的数据结构&#xff1a; 假如data的数据是下面的结构&#xf…

【Unity引擎核心-Object,序列化,资产管理,内存管理】

文章目录 整体介绍Native & Managed Objects什么是序列化序列化用来做什么Editor和运行时序列化的区别脚本序列化针对序列化的使用建议 Unity资产管理导入Asset Process为何要做引擎资源文件导入Main-Assets和 Sub-Assets资产的导入管线Hook&#xff0c;AssetPostprocessor…

傅里叶变换和其图像处理中的应用

以下部分文字资料整合于网络&#xff0c;本文仅供自己学习用&#xff01; 一、为什么要在频域进行图像处理&#xff1f; 一些在空间域表述困难的增强任务&#xff0c;在频率域中变得非常普通 滤波在频率域更为直观&#xff0c;你想想嘛&#xff0c;所谓滤波&#xff0c;就是…

Spring Boot Bean 注入的常用方式教程

Spring Boot Bean 注入是一种将依赖对象引入到应用程序组件中的机制&#xff0c;它有助于实现松耦合和可测试的代码。这种注入方式允许我们将依赖关系委托给 Spring 容器来管理&#xff0c;从而提高了代码的可维护性和可读性。Spring Boot 提供了多种 Bean 注入方式&#xff0c…

Linux-CentOS8-Oracle19c 安装详解

Linux-CentOS8-Oracle19c安装图解 文章目录 Linux-CentOS8-Oracle19c安装图解预备1. Oracle19c 安装手册&#xff1a;2. 安装虚拟机&#xff1a;4G内存&#xff0c;2*2核心&#xff0c;30G3. 下载CentOS8镜像。4. 开始准备预安装5. 修改Oracle账户密码6. 修改SELINUX值在文件&a…

AWS SAP-C02教程2--存储资源

存储资源在架构设计中是一个少不了的环节,而在AWS中有不同类型的存储资源,对应会有不同用途不同价格,SAP考试中考察各种存储是少不了,以下是涉及到的存储 目录 1 非结构化存储1.1 EBS(块存储)1.1.1 基本限制1.1.2 类型1.1.3 RAID 配置选项1.1.4 Snapshot1.2 Local Insta…

thinkphp6 入门(8)-- Session

开启Session Session功能默认是没有开启的&#xff08;API应用通常不需要使用Session&#xff09; think\middleware\SessionInit// 添加引用 use think\facade\Session; 赋值 Session::set(name, thinkphp);取值 // 如果值不存在&#xff0c;返回null Session::get(name)…

CSS的布局 Day03

一、显示模式&#xff1a; 网页中HTML的标签多种多样&#xff0c;具有不同的特征。而我们学习盒子模型、使用定位和弹性布局把内容分块&#xff0c;利用CSS布局使内容脱离文本流&#xff0c;使用定位或弹性布局让每块内容摆放在想摆放的位置&#xff0c;让网站页面布局更合理、…

ESP32-IPS彩屏ST7789-Arduino-简单驱动

目的&#xff1a; 使ESP32能够驱动点亮ST7789显示屏 前提条件&#xff1a; ESP32 ST7789 &#xff08;240 x240&#xff0c;IPS&#xff09; 杜邦线 Arduino 过程&#xff1a; 0x00--接线 0x01--驱动&#xff1a; 彩屏驱动库 针对不同的彩屏驱动芯片&#xff0c;常用的 Arduino…

如何实现 Es 全文检索、高亮文本略缩处理(封装工具接口极致解耦)

如何实现 Es 全文检索、高亮文本略缩处理 前言技术选型JAVA 常用语法说明全文检索开发高亮开发Es Map 转对象使用核心代码 Trans 接口&#xff08;支持父类属性的复杂映射&#xff09;Trans 接口可优化的点高亮全局配置类如下真实项目落地效果为什么不用 numOfFragments、fragm…

深度强化学习 第 3 章 强化学习基本概念

本章讲解强化学习的基本概念。第 3.1 节介绍马尔可夫决策过程&#xff08;Markov decision process&#xff0c;简称 MDP&#xff09;&#xff0c;它是最常见的对强化学习建模的方法。第 3.2 节定义策略函数&#xff0c;包括随机策略和确定策略。第 3.3 节分析强化学习中的随机…

element树形控件编辑节点组装节点

需求功能&#xff1a; 编辑树节点&#xff0c;组装节点 <el-scrollbar class"scrollbar-wrapper"><el-tree :data"nodeList" ref"tree" :props"defaultProps" :expand-on-click-node"false"><template slot…

十三、队列的特性

1、队列(Queue)概述 队列可以用于"任务到任务"、"任务到中断"、"中断到任务"直接传输信息。队列涉及内容如下: 怎么创建、清楚、删除队列队列中消息如何保存怎么向队列发送数据、怎么从队列读取数据、怎么覆盖队列的数据在队列上阻塞式什么意思…

计算机毕业设计-开题报告答辩常见问题!Javaweb项目答辩

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

尚硅谷Flink(一)

目录 ☄️前置工作 fenfa脚本 &#x1f30b;概述 ☄️Flink是什么 ☄️特点&#xff08;多nb&#xff09; ☄️应用场景&#xff08;不用看&#xff09; ☄️分层API &#x1f30b;配环境 ☄️wordcount ☄️WcDemoUnboundStreaming &#x1f30b;集群部署 ☄️集…

计算机毕业设计 基于协同过滤算法的白酒销售系统的设计与实现 Javaweb项目 Java实战项目 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

IP 协议的相关特性(部分)

IP 协议的报文格式 4位版本号&#xff1a; 用来表示IP协议的版本&#xff0c;现有的IP协议只有两个版本&#xff0c;IPv4&#xff0c;IPv6。 4位首部长度&#xff1a; 设定和TCP的首部长度一样 8位服务类型&#xff1a; &#xff08;真正只有4位才有效果&#xff09;&#xf…

【LeetCode 算法专题突破】双指针(⭐)

文章目录 前言1. 移动零题目描述代码 2. 复写零题目描述代码 3. 快乐数题目描述代码 4. 盛最多水的容器题目描述代码 5. 有效三角形的个数题目描述代码 6. 三数之和题目描述代码 7. 四数之和题目描述代码 总结 前言 学算法入门必学的一个章节&#xff0c;双指针算法&#xff0…

2023/10/15总结

学习总结 最近开始写项目了&#xff0c;然后写的过程中遇到了跨域问题。 为什么会出现跨域问题 由于浏览器的同源策略限制。同源策略是一种约定&#xff0c;它是浏览器最核心也是最基本的安全功能。如果缺少了同源策略&#xff0c;那么浏览器的正常功能可能都会收到影响。所谓…