前言
本文简单介绍设计模式中的工厂方法的实现方式及应用
文中所引用的模块及需要注意的事项:
1、python标准库中的Typing模块及其子类Callable,此模块用于在声明变量时同时声明其变量类型。需要对变量类型的声明有一定了解。
2、python标准库中的abc模块abstractmethod装饰器,用于声明抽象类
3、工厂方法是对编码方式的一种提升方式,你可以通过自己的方式来随意的创建类及实例,但无规矩不成方圆,想要方便的调试代码减少bug及方便自己阅读或想方便别人同你合作,就要不断尝试规范自己的代码
4、对可变参数的打包及解包有一定了解
一、工厂方法简单介绍
通过一个简单例子来作说明:假如有三个人,小明,小明的爸爸及小明的老师,小明每天需要按时去学校上学,小明的爸爸每天需要去送小明上学,小明的老师在学校等待小明来学校报到,如何通过程序来描述三个人的行为?同时抽象出这种行为后可以扩展下,可以定义某名同学去的上学,送其上学的是其他人,在学校等待报到的有可能是其他老师,如何创建相应的实例来描述这些行为?
1、 统计信息
将三个人的信息进行分析,找到三个人身份的共同点及不同点,三个人各自不同点有其名字及身份,相同点是三个人作为不同身份的人都有其相应的职责,学生需要上学,爸爸需要送孩子上学,老师需要学生来学校报到,抽离出所用的信息:
people_data = {
'people': [
{'name': 'XiaoMing', 'identity': 'Student'},
{'name': 'DaMing', 'identity': 'Parent'},
{'name': 'TeacherWang', 'identity': 'Teacher'}
]
}
我们通过所统计的个体信息进行实例创建,三个人作为人的个体都有名字及身份,可抽象出一个父类人,再从父类中继承作为人的属性,根据不同身份的职责扩展出其子类(学生,爸爸,老师),在子类下定义不同身份下的职责。
2、简单实现
from abc import abstractmethod
# 创建工厂类的抽象类
class Person:
def __init__(self, name, identity):
self.name = name
self.identity = identity
@abstractmethod
def action(self):
"""Person action"""
def __str__(self):
return f'{self.name} {self.identity}'
# 创建工厂类的具体类
class Student(Person):
# def __init__(self, name, identity):
# super().__init__(name, identity)
def action(self) -> None:
print(f'{self.name} is going to school')
# 创建工厂类的具体类
class Parent(Person):
# def __init__(self, name, identity):
# super().__init__(name, identity)
def action(self) -> None:
print(f'{self.name} is sending children to school')
# 创建工厂类的具体类
class Teacher(Person):
# def __init__(self, name, identity):
# super().__init__(name, identity)
def action(self) -> None:
print(f'{self.name} is waiting student to school')
# 此处为简单的工厂实现但在大数据量的情况下违背了对扩展开放对修改封闭的原则,业务逻辑改动量较大
if __name__ == '__main__':
# 声明要创建的类的数据
peopleData = {
'people': [
{'name': 'XiaoMing', 'identity': 'Student'},
{'name': 'DaMing', 'identity': 'Parent'},
{'name': 'TeacherWang', 'identity': 'Teacher'}
]
}
insPersons = []
for item in peopleData['people']:
if item['identity'] == 'Student':
insPersons.append(Student(**item))
elif item['identity'] == 'Parent':
insPersons.append(Parent(**item))
elif item['identity'] == 'Teacher':
insPersons.append(Teacher(**item))
for item_person in insPersons:
print(item_person)
item_person.action()
最常想到的创建类的方式或许通过以上或类似的方式,从以上示例中我们可以拆分出三个业务模块,
1、抽象类声明,如上示例中的Person类,用于概括所要创建类的共同点
2、具体类,即示例中的Student,Parent,Teacher子类,其包含了各自的方法(职责)
3、工厂方法,即示例中根据信息创建实例的For语句
如果后面有爷爷来送孩子上学,我们需要有哪些改动?但考虑到以后代码的可维护性及扩展性可以怎样优化?
如果需要添加子类(如添加爷爷类),那么需要在具体类里添加子类及修改工厂方法,代码设计有一个原则‘对扩展开放,对修改封闭’,即工厂方法应当只关注于创建实例的接口并不关心实例应当如何实现,再回顾下示例中创建实例的方法:其中For 循环其实包含了代码的坏味道,其代码是工厂方法的实现,但在用户调用时已经参与到了工厂方法中,最好的方式是用户调用时只调用接口并不能修改工厂方法
for item in peopleData['people']:
if item['identity'] == 'Student':
insPersons.append(Student(**item))
elif item['identity'] == 'Parent':
insPersons.append(Parent(**item))
elif item['identity'] == 'Teacher':
insPersons.append(Teacher(**item))
二、进一步优化
我们尝试对工厂方法进行进一步优化,将工厂方法对客户进行封闭,这样保证用户只关心调用,这样才能更符合‘对扩展开放,对修改封闭’的原则
在这里我们将会创建一个新的python模块,在此模块中对工厂方法进行封装,并对外开放接口。在这里我们通过在工厂方法里添加引用 类型字典的方式并创建为字典添加引用类型的方法Resgister,Unregister进行类型的添加及注销,并开放Create方法方便客户进行调用来生成实例,这样客户只能通过Register或Unregister方式来扩展工厂方法但并不会直接修改其代码,保证了对修改封闭的原则的同时也对外保证了对扩展开放。
具体实现
代码实现将代码分为了三个部分,一个是抽象类及具体类声明部分Person_AbstractClass.py,第二部分为工厂方法部分Person_FactoryMethod.py,第三部分是用户调用部分Person_FactoryRealize.py
1、抽象类及具体类部分
此处与上示例的声明部分相同
Person_AbstractClass.py
from abc import abstractmethod
# 创建工厂类的抽象类
class Person:
def __init__(self, name, identity):
self.name = name
self.identity = identity
@abstractmethod
def action(self):
"""Person action"""
def __str__(self):
return f'{self.name} {self.identity}'
# 创建工厂类的具体类
class Student(Person):
# def __init__(self, name, identity):
# super().__init__(name, identity)
def action(self) -> None:
print(f'{self.name} is going to school')
# 创建工厂类的具体类
class Parent(Person):
# def __init__(self, name, identity):
# super().__init__(name, identity)
def action(self) -> None:
print(f'{self.name} is sending children to school')
# 创建工厂类的具体类
class Teacher(Person):
# def __init__(self, name, identity):
# super().__init__(name, identity)
def action(self) -> None:
print(f'{self.name} is waiting student to school')
2、工厂方法的封装
Person_FactoryMethod.py
from typing import Callable, Any
from Person_AbstractClass import Person
# 创建存储类型的字典,根据‘对扩展开放,对扩展封闭’的原则,在以后需要扩展Person子类时此模块不需要再更改,
# 只需要调用Register,Unregister注册或注销即可
# 关于Callable后面的省略号作用为忽略参数声明,但指定返回为Person类型
person_create_function: dict[str, Callable[..., Person]] = {}
# 向person_create_function 字典注册类型, 通过设置Callable返回基类类型Person就可以在使用register时返回子类类型
def register(identity: str, creation_function: Callable[..., Person]) -> None:
person_create_function[identity] = creation_function
# 向person_create_function 字典注销类型
def unregister(identity: str) -> None:
person_create_function.pop(identity, None)
# 通过存储数据的字典来创建实例
def create(args: dict[str, Any]) -> Person:
the_args = args.copy()
identity = the_args['identity']
create_function = person_create_function[identity]
return create_function(**the_args)
3、用户调用接口
Person_FactoryRealize.py
from Person_AbstractClass import Person, Student, Parent, Teacher
from Person_FactoryMethod import register, unregister, create
def main():
"""
the main function for modify the factory method and create the instance
:return: None
"""
people_data = {
'people': [
{'name': 'XiaoMing', 'identity': 'Student'},
{'name': 'DaMing', 'identity': 'Parent'},
{'name': 'TeacherWang', 'identity': 'Teacher'}
]
}
# 通过Register方法来向工厂类中注册所需要使用的类型
register('Student', Student)
register('Parent', Parent)
register('Teacher', Teacher)
# 通过数据参数来创建类实例
for item_person in people_data['people']:
ins_person = create(item_person)
print(ins_person)
ins_person.action()
if __name__ == '__main__':
main()
总结
工厂方法的创建及对’对扩展开放,对修改封闭’原则的优化部分结束