【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,我们就可以先开个抽象类

python 复制代码
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(),这样子是非常没有效率的...,能不能把这个作业系统的环境提取出来呢???

2.2 关于工厂

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

python 复制代码
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()

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

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

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

2.3 完整代码:

python 复制代码
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, 早就已经透过工厂创造好啦

2.4 总结

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

设计模式

抽象工厂模式

三、什么是类工厂

3.1 类工厂定义

  • 类工厂本质

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

  • 类工厂函数

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

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

使用type创建类如下

python 复制代码
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,则可以将其封装放入一个函数中。如下:

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

下列示例表示

python 复制代码
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()

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

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

python 复制代码
    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'>

3.5 编写类工厂的时机

  • 在需要基于运行时的信息(如用户输入)创建类时需要编写类工厂
    • 如果在编码时并不知道需要赋值给类的属性时
    • 类工厂示例:(创建类工厂的原因:假如说是为大量不同的第三方网站提供凭据的服务,则需要有多重不同的验证方式。该工厂可以根据数据库查询结果生成属性)
python 复制代码
#-*- 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'>

  • 避免类属性一致性问题:处理类与实例之间属性不同的问题
python 复制代码
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,而将这个方法当成一个普通的函数使用

python 复制代码
#-*- 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

四、类方法限制

python 复制代码
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

4.1 使用类工厂

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

python 复制代码
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类方法创建类的方式返回需要的结果。

4.2 单例模式

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

类工厂示例:

python 复制代码
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.当你想要创建一个可扩展的关联在创建类与支持创建对象的类之间。
python 复制代码
"""
工厂模式:
根据需求产生对象。
"""
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())
相关推荐
寻星探路10 小时前
【深度长文】万字攻克网络原理:从 HTTP 报文解构到 HTTPS 终极加密逻辑
java·开发语言·网络·python·http·ai·https
崔庆才丨静觅11 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606112 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了12 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅12 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
ValhallaCoder12 小时前
hot100-二叉树I
数据结构·python·算法·二叉树
wdfk_prog12 小时前
[Linux]学习笔记系列 -- [drivers][input]input
linux·笔记·学习
崔庆才丨静觅12 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
盟接之桥13 小时前
盟接之桥说制造:引流品 × 利润品,全球电商平台高效产品组合策略(供讨论)
大数据·linux·服务器·网络·人工智能·制造
猫头虎13 小时前
如何排查并解决项目启动时报错Error encountered while processing: java.io.IOException: closed 的问题
java·开发语言·jvm·spring boot·python·开源·maven