简介
创建型设计模式处理对象创建相关的问题,目标是当直接创建对象(Python中是通过__init__()
函数实现的)不太方便,提供更好的方式。
在工程设计模式中,客户端可以请求一个对象,而无需知道这个对象来自哪里;也就是,使用哪个类来生成的这个对象。工厂背后的思想是简化对象的创建。与客户端自己基于类实例化直 接创建对象相比,基于一个中心化函数来实现,更易于追踪创建了哪些对象(我们可以在这个函数创建对象的时候,来进行记录)。通过将创建对象的代码和使用对象的代码解耦,工厂能够降低应用维护的复杂度。
工厂通常有两种形式:
1.工厂方法(Factory Method)
它是一个方法(术语来说,是一个函数),对不同的输入参数返回不同的对象。
2.抽象工厂
它是一组用于创建一系列相关事物对象的工厂方法和网页。
1.工厂方法
在工厂方法模式中,我们执行单个函数,传入一个参数(提供信息表明我们想要什么),但
并不要求知道任何关于对象如何实现以及对象来自哪里的细节。
现实生活的例子
现实中用到工厂方法模式思想的一个例子是塑料玩具制造。制造塑料玩具的压塑粉都是一样 的,但使用不同的塑料模具就能产出不同的外形。
eg.有一个工厂方法,输入是目标外形(鸭 子或小车)的名称,输出则是要求的塑料外形。
图片.png软件的例子
Django框架使用工厂方法模式来创建表单字段。
Django的forms模块支持不同种类字段 (CharField、EmailField)的创建和定制(max_length、required)。
class MyModel(models.Model):
firstDate = models.DateTimeField(auto_now_add=True)
another = models.CharField(max_length=30)
应用案例
1.如果因为应用创建对象的代码分布在多个不同的地方,而不是仅在一个函数/方法中,你发 现没法跟踪这些对象,那么应该考虑使用工厂方法模式。
工厂方法集中地在一个地方创建对象,使对象跟踪变得更容易。注意,创建多个工厂方法也完全没有问题,实践中通常也这么做,对相似的对象创建进行逻辑分组,每个工厂方法负责一个分组。例如, 有一个工厂方法负责连接到不同的数据库(MySQL、SQLite),另一个工厂方法负责创建要求的 几何对象(圆形、三角形),等等。
2.若需要将对象的创建和使用解耦,工厂方法也能派上用场。
创建对象时,我们并没有与某个特定类耦合/绑定到一起,而只是通过调用某个函数来提供关于我们想要什么的部分信息。
这意:味着修改这个函数比较容易,不需要同时修改使用这个函数的代码。
3.另外一个值得一提的应用案例与应用性能及内存使用
相关。工厂方法可以在必要时创建新的 对象,从而提高性能和内存使用率。
若直接实例化类来创建对象, 那么每次创建新对象就需要分配额外的内存(除非这个类内部使用了缓存,一般情况下不会这 样)。用行动说话,下面的代码(文件id.py)对同一个类A创建了两个实例,并使用函数id()比 较它们的内存地址。输出中也会包含地址,便于检查地址是否正确。内存地址不同就意味着创建 了两个不同的对象。
实现
数据来源可以有多种形式。存取数据的文件主要有两种分类:人类可读文件和二进制文件。
1.人类可读文件:
XML、Atom、YAML和JSON。(两种流行的人类可读文件格式:XML和JSON)
虽然人类可读文件解析起来 通常比二进制文件更慢,但更易于数据交换、审查和修改。
2.二进制文件:
SQLite使用的.sq3
文件格式,及用于听音乐的.mp3
文件格式。
案例:
import json
import xml.etree.ElementTree as etree
class JSONConnector:
def __init__(self, filepath):
self.data = dict()
with open(filepath, mode='r', encoding='utf-8') as f:
self.data = json.load(f)
@property
def parsed_data(self):
return self.data
class XMLConnector:
def __init__(self, filepath):
self.tree = etree.parse(filepath)
@property
def parsed_data(self):
return self.tree
# 函数`connection_factory`是一个工厂方法,基于输入文件路径的扩展名返回一个`JSONConnector`或`XMLConnector`的实例,如下所示。
def connection_factory(filepath):
if filepath.endswith('json'):
connector = JSONConnector
elif filepath.endswith('xml'):
connector = XMLConnector
else:
raise ValueError('Cannot connect to {}'.format(filepath))
return connector(filepath)
# 函数connect_to()对connection_factory()进行包装,添加了异常处理,如下所示。
def connect_to(filepath):
factory = None
try:
factory = connection_factory(filepath)
except ValueError as e:
print(e)
return factory
注意,虽然JSONConnector和XMLConnector拥有相同的接口,但是对于parsed_data() 返回的数据并不是以统一的方式进行处理。
对于每个连接器,需使用不同的Python代码来处理。 若能对所有连接器应用相同的代码当然最好,但是在多数时候这是不现实的,除非对数据使用某 种共同的映射,这种映射通常是由外部数据提供者提供。
总结:
工厂方法的工厂要返回的对象的类是相似的,JSONConnector
和XMLConnector
都有parsed_data
,但是parsed_data
的数据格式可以不一样,我们所以应该再加一个@property
用于解释数据是什么格式。
2.抽象工厂
抽象工厂设计模式是抽象方法的一种泛化。
概括来说,一个抽象工厂是(逻辑上的)一组工厂方法,其中的每个工厂方法负责产生不同种类的对象。
现实生活中的例子
汽车制造业应用了抽象工厂的思想。冲压不同汽车模型的部件(车门、仪表盘、车篷、挡泥板及反光镜等)所使用的机件是相同的。
机件装配起来的模型随时可配置,且易于改变。从下图 我们能看到汽车制造业抽象工厂的一个例子。
里面的组件的模型都是可以改变的。
软件的例子
程序包django_factory
(https://pypi.org/project/django_factory/0.7/)是一个用于在测试中创建Django模型的抽象工厂实现,可以用来为支持测试专有属性的模型创建实例。这能让测试代码的可读性更高,且能避免共享不必要的代码,故有 其存在的价值。
应用案例
因为抽象工厂模式是工厂方法模式的一种泛化,所以它能提供相同的好处:让对象的创建更
容易追踪;将对象创建与使用解耦;提供优化内存占用和应用性能的潜力。
我们怎么知道何时该使用工厂方法,何时又该使用抽象工厂?
答案是, 通常一开始时使用工厂方法,因为它更简单。如果后来发现应用需要许多工厂方法,那么将创建 一系列对象的过程合并在一起更合理,从而最终引入抽象工厂。
抽象工厂有一个优点,在使用工厂方法时从用户视角通常是看不到的,那就是抽象工厂能够 通过改变激活的工厂方法动态地(运行时)改变应用行为。一个经典例子是能够让用户在使用应 用时改变应用的观感(比如,Apple风格和Windows风格等),而不需要终止应用然后重新启动。
实现
eg:
class Frog:
def __init__(self, name):
self.name = name
def __str__(self): # 打印self,就会答应下面的self.name。参考下面的`interact_with`方法
return self.name
def interact_with(self, obstracle):
print('{} the Frog encounters {} and {}!'.format(self, obstracle, obstracle.action()))
class Bug:
def __str__(self):
return 'a bug'
def action(self):
return 'eats it'
# 类FrogWorld是一个抽象工厂,主要职责是创建游戏的主人公青蛙和障碍物。区分创建方法并
# 使其名字通用(eg. make_character() 和 make_obstacle()),这让我们可以动态地改变激活的
# 工厂(也因此改变了当前激活的游戏),而无需进行任何代码变更。在这一门静态语言中,抽象工厂是一个
# 抽象类/接口,具备一些空方法,但在Python中无需如此,因为类型是在运行时检测的。
class FrogWorld:
def __init__(self, name):
print(self)
self.player_name = name
def __str__(self):
return '\n\n\t------Frog World------'
def make_character(self): #character:角色
return Frog(self.player_name)
def make_obstancle(self):
return Bug()
class Wizard:
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
def interact_with(self, obstacle):
print('{} the Wizard battles against {} and {}!'.format(self, obstacle, obstacle.action()))
class Ork:
def __str__(self):
return 'an evil ork'
def action(self):
return 'kills it'
class WizardWorld:
def __init__(self, name):
print(self)
self.player_name = name
def __str__(self):
return '\n\n\t------ Wizard World -------'
def make_character(self):
return Wizard(self.player_name)
def make_obstacle(self):
return Ork()
class GameEnvironment:
def __init__(self, factory):
self.here = factory.make_character()
self.obstacle = factory.make_obstacle()
def play(self):
self.here.interact_with(self.obstacle)
def validate_age(name):
try:
age = input('Welcome {}. How old are you?'.format(name))
age = int(age)
except ValueError as e:
print("Age {} is invalid, please try again ...".format(age))
return (True, age)
def main():
name = input("Hello. What's your name?")
valid_input = False
while not valid_input:
valid_input, age = validate_age(name)
game = Frog if age < 18 else WizardWorld
environment = GameEnvironment(game(name)) # game 是对多种的抽象
environment.play()
if __name__ == '__main__':
main()
总结:这里是多种游戏,我们使用一个类将两种游戏抽象出来。
小结
我们学习了如何使用工厂方法和抽象工厂设计模式。
两种模式都可以用于以下几种 场景:
(a)想要追踪对象的创建时;
(b)想要将对象的创建与使用解耦时;
(c)想要优化应用的性能 和资源占用时;
工厂方法设计模式的实现是一个不属于任何类的单一函数,负责单一种类对象(一个形状、 一个连接点或者其他对象)的创建。
我们看到工厂方法是如何与玩具制造相关联的,提到Django 是如何将其用于创建不同表单字段的,并讨论了其他可能的应用案例。作为示例,我们实现了一 个工厂方法,提供了访问XML和JSON文件的能力。
抽象工厂设计模式的实现是同属于单个类的许多个工厂方法用于创建一系列种类的相关对 象(一辆车的部件、一个游戏的环境,或者其他对象)。我们提到抽象工厂如何与汽车制造业相 关联,Django程序包django_factory是如何利用抽象工厂创建干净的测试用例,并学习了抽象 工厂的应用案例。作为抽象工厂实现的示例,我们完成了一个迷你游戏,演示了如何在单个类中使用多个相关工厂
。
网友评论