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


    一、说明

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

    二、关于抽象类的使用案例

    2.1 抽象类

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

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

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

    1. class Button(ABC):
    2. @abstractmethod
    3. def clicked(self):
    4. pass
    5. class WinButton(Button):
    6. def clicked(self):
    7. print('click winbutton')
    8. class MacButton(Button):
    9. def clicked(self): print('click macbutton')

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

    2.2 关于工厂

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

    1. class GUIFactory(ABC):
    2. @abstractmethod
    3. def create_button(self):
    4. pass
    5. @abstractmethod
    6. def create_checkbox(self):
    7. pass
    8. class WinFactory(GUIFactory):
    9. def create_button(self):
    10. return WinButton()
    11. def create_checkbox(self):
    12. return WinCheckBox()
    13. class MacFactory(GUIFactory):
    14. def create_button(self):
    15. return MacButton()
    16. def create_checkbox(self):
    17. return MacCheckBox()

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

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

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

    2.3 完整代码: 

    1. from abc import ABC, abstractmethod
    2. class GUIFactory(ABC):
    3. @abstractmethod
    4. def create_button(self):
    5. pass
    6. @abstractmethod
    7. def create_checkbox(self):
    8. pass
    9. class WinFactory(GUIFactory):
    10. def create_button(self):
    11. return WinButton()
    12. def create_checkbox(self):
    13. return WinCheckBox()
    14. class MacFactory(GUIFactory):
    15. def create_button(self):
    16. return MacButton()
    17. def create_checkbox(self):
    18. return MacCheckBox()
    19. class Button(ABC):
    20. @abstractmethod
    21. def clicked(self):
    22. pass
    23. class WinButton(Button):
    24. def clicked(self):
    25. print('click winbutton')
    26. class MacButton(Button):
    27. def clicked(self):
    28. print('click macbutton')
    29. class CheckBox(ABC):
    30. @abstractmethod
    31. def checked(self):
    32. pass
    33. class WinCheckBox(CheckBox):
    34. def checked(self):
    35. print('check wincheckbox')
    36. class MacCheckBox(CheckBox):
    37. def checked(self):
    38. print('check maccheckbox')
    39. class Application:
    40. def __init__(self, factory):
    41. self.button = factory.create_button()
    42. self.checkbox = factory.create_checkbox()
    43. if __name__ == "__main__":
    44. app = Application(MacFactory())
    45. app.button.clicked()

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

    2.4 总结

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

    设计模式

    抽象工厂模式

    三、什么是类工厂

    3.1 类工厂定义

    • 类工厂本质

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

    • 类工厂函数

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

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

    使用type创建类如下

    1. def init(self, name):
    2. self.name = name
    3. def eat(self):
    4. pass
    5. def go_to_vet(self):
    6. print "go_to_vet"
    7. return type('Animal', (object,), {
    8. '__doc__': 'A class representing an arbitrary animal.',
    9. '__init__': init,
    10. 'eat': eat,
    11. 'go_to_vet': go_to_vet,
    12. })

    这种方式的缺点:

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

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

    下列示例表示

    1. def create_animal_class():
    2. def init(self, name):
    3. self.name = name
    4. def eat(self):
    5. pass
    6. def go_to_vet(self):
    7. print(self.name)
    8. return type('Animal', (object,), {
    9. '__doc__': 'A class representing an arbitrary animal.',
    10. '__init__': init,
    11. 'eat': eat,
    12. 'go_to_vet': go_to_vet,
    13. })
    14. Animal1 = create_animal_class( )
    15. dog = Animal1("my dog")
    16. dog.go_to_vet()
    17. cat = Animal1("my cat")
    18. cat.go_to_vet()

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

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

    1. def create_animal_class():
    2. class Animal(object):
    3. def init(self, name):
    4. self.name = name
    5. def eat(self):
    6. pass
    7. def go_to_vet(self):
    8. print "go_to_vet"
    9. return Animal
    10. Animal = create_animal_class()
    11. print Animal #

    3.5 编写类工厂的时机

    • 在需要基于运行时的信息(如用户输入)创建类时需要编写类工厂
      • 如果在编码时并不知道需要赋值给类的属性时
      • 类工厂示例:(创建类工厂的原因:假如说是为大量不同的第三方网站提供凭据的服务,则需要有多重不同的验证方式。该工厂可以根据数据库查询结果生成属性) 
    1. #-*- coding:utf-8 -*-
    2. def get_credential_class(use_proxy=False, tfa=False):
    3. if use_proxy:
    4. keys = ['service_name', 'email_address'] # 通过代理身份验证所要的密匙
    5. else:
    6. keys = ['username', 'password']
    7. if tfa:
    8. keys.append('tfa_token')
    9. class Credential(object):
    10. expected_keys = set(keys)
    11. def __init__(self, **kwargs):
    12. if self.expected_keys != set(kwargs.keys()):
    13. raise ValueError('Keys do not match')
    14. for k, v in kwargs.items():
    15. setattr(self, k, v)
    16. return Credential
    17. cred = get_credential_class(0,0)
    18. print(cred)

    运行结果:
    main.Credential’>

    • 避免类属性一致性问题:处理类与实例之间属性不同的问题
    1. class C(object):
    2. foo = 'bar'
    3. class I(object):
    4. def __init__(self):
    5. self.foo = 'bar'
    6. print C.foo()
    7. print I.foo() # AttributeError
    8. c1 = C()
    9. c2 = C()
    10. c1.foo = 'baz'
    11. print c1.foo #baz
    12. print c2.foo # bar
    13. C.foo = 'bacon'
    14. print c1.foo #baz
    15. print c2.foo #bacon
    16. print c1.__dict__ # {'foo': 'baz'}
    17. 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,而将这个方法当成一个普通的函数使用
     

    1. #-*- coding:utf-8 -*-
    2. class cls:
    3. clsvar = 1 #普通字段
    4. def __init__(self):
    5. self.insvar = 2
    6. ins1 = cls()
    7. ins2 = cls()
    8. ins1.clsvar = 20
    9. print cls.clsvar #输出结果为1
    10. print ins1.clsvar #输出结果为20
    11. print ins2.clsvar #输出结果为1
    12. #用类名为类变量重新赋值并打印
    13. cls.clsvar = 10
    14. print cls.clsvar #输出结果为10
    15. print ins1.clsvar #输出结果为20
    16. print ins2.clsvar #输出结果为10
    17. #这次直接给实例1没有在类中定义的变量赋值
    18. ins1.x = 11
    19. print ins1.x #输出结果为11
    20. #然后再用类名给类中没有定义的变量赋值
    21. cls.m = 21
    22. print cls.m #输出结果为21
    23. #再创建一个实例ins3,然后打印一下ins3的变量
    24. ins3 = cls()
    25. print ins3.insvar #输出结果为2
    26. print ins3.clsvar #输出结果为10
    27. print ins3.m #输出结果为21
    28. print ins3.x #报错AttributeErro

    四、类方法限制 

    1. class C(object):
    2. foo = 'bar'
    3. @classmethod
    4. def classfoo(cls):
    5. return cls.foo
    6. print c1.classfoo() #bacon
    7. print c2.classfoo() #bacon

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

    4.1 使用类工厂

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

    1. class C(object):
    2. foo = 'bar'
    3. @classmethod
    4. def classfoo(cls):
    5. return cls.foo
    6. def create_C_subclass(new_foo):
    7. class SubC(C):
    8. foo = new_foo
    9. return SubC
    10. S = create_C_subclass('spam')
    11. print S.classfoo() #spam
    12. E = create_C_subclass('eggs')
    13. print E.classfoo() #eggs

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

    4.2 单例模式

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

    1. class C(object):
    2. foo = 'bar'
    3. @classmethod
    4. def classfoo(cls):
    5. return cls.foo
    6. def CPrime(new_foo='bar'):
    7. if new_foo == 'bar':
    8. return C()
    9. class SubC(C):
    10. foo = new_foo
    11. return SubC
    12. EE = CPrime('bar')
    13. FF = CPrime('bar1')
    14. print EE #<__main__.C object at 0x01777CB0>
    15. print FF #

    五、实例的工厂模式

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

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

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

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

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

    • 1.不知道用户想要创建什么样的对象
    • 2.当你想要创建一个可扩展的关联在创建类与支持创建对象的类之间。
    1. """
    2. 工厂模式:
    3. 根据需求产生对象。
    4. """
    5. from typing import Any
    6. class Clothes:
    7. """服装工厂类"""
    8. def __init__(self,name):
    9. self.name = name
    10. def create(self):
    11. pass
    12. class Lovely(Clothes):
    13. def __init__(self, name):
    14. super().__init__(name)
    15. def create(self):
    16. print(f"{self.name} 生产 汉服")
    17. class Cool(Clothes):
    18. def __init__(self, name):
    19. super().__init__(name)
    20. def create(self):
    21. print(f"{self.name} 生产 酷酷的、帅")
    22. class Lipstick(Clothes):
    23. def __init__(self, name):
    24. super().__init__(name)
    25. def create(self):
    26. print(f"{self.name} 生产 死亡芭比粉")
    27. class Shoes(Clothes):
    28. def __init__(self, name):
    29. super().__init__(name)
    30. def create(self):
    31. print(f"{self.name}生产 红色高跟鞋")
    32. class Shoes2(Clothes):
    33. def __init__(self, name):
    34. super().__init__(name)
    35. def create(self):
    36. print(f"{self.name}生产 蓝色高跟鞋")
    37. class Requirement:
    38. """定义一个需求类"""
    39. @staticmethod
    40. def creatNeed(need):
    41. if need == "死亡芭比粉":
    42. return Lipstick("死亡芭比粉")
    43. elif need == "酷酷的、帅":
    44. return Cool("酷酷的、帅")
    45. elif need == "汉服":
    46. return Lovely("汉服")
    47. elif need == "红色高跟鞋":
    48. return Shoes("红色高跟鞋")
    49. elif need == "蓝色高跟鞋":
    50. return Shoes2("蓝色高跟鞋")
    51. # Requirement.creatNeed("死亡芭比粉").create()
    52. # Requirement.creatNeed("红色高跟鞋").create()
    53. # Requirement.creatNeed("汉服").create()
    54. with open("create.txt","r",encoding="utf-8")as f_r:
    55. content = f_r.read()
    56. # print(content)
    57. Requirement.creatNeed(content).create()
    58. # class StudentNum:
    59. # num = 0
    60. #
    61. # @classmethod
    62. # def add_num(cls):
    63. # cls.num += 1
    64. #
    65. # @classmethod
    66. # def get_num(cls):
    67. # return cls.num
    68. #
    69. # def __new__(cls) -> Any:
    70. # StudentNum.add_num()
    71. # return super().__new__(cls)
    72. #
    73. #
    74. # class Student(StudentNum):
    75. # def __init__(self):
    76. # self.name = ""
    77. # a = Student()
    78. # b = Student()
    79. # c = Student()
    80. # print(StudentNum.get_num())

  • 相关阅读:
    DiffusionDet: Diffusion Model for Object Detection
    武义县城区棚户区改造溪南区块安置房建设项目(标段一)电能管理系统的研究及应用
    Qt5开发及实例V2.0-第八章-Qt模型/视图结构
    【Android 图像显示系统】整体架构与缓冲区策略
    Tomcat部署及优化
    vxe-table添加排序
    java回收算法学习
    Mybatis中 collection 和 association 标签 的区别
    使用消息队列的方式实现进程间通信,输入quit时退出
    MSFvenom恶意程序生成与利用
  • 原文地址:https://blog.csdn.net/gongdiwudu/article/details/133867450