python设计模式之工厂模式
1.工厂方法
在工厂模式中,我们执行单个函数,传入一个参数(提供信息表明我们想要什么),但并不要求知道任何关于实现以及对象来自哪里的细节。
1.1.1 现实生活中的例子
现实中用到工厂模式思想的一个例子是塑料玩具制造。制造塑料玩具的压塑粉都是一样的,但使用不同的塑料模具就可以产生出不同的形状。比如,有一个工厂方法,输入的是目标的形状的名称,输出则是要求的塑料外形。
1.1.2 软件的例子
Django框架使用工厂方法模式来创建表单字段。Django的forms模块支持不同种类字段的创建和控制。
1.1.3 应用案例
如果以为应用创建对象的代码分布在不同的地方,而不仅在一个函数/方法中,你发现没法跟踪这些对象,那么应该考虑只用工厂方法模式。工厂方法集中的在一个地方创建对象,使得对象的跟踪更加的容易。
若需要将对象的创建和使用解耦,工厂方法也能派上用场。创建对象时,我们并没有与某个特定类耦合/绑定到一起,而只是通过调用某个函数来提供关于我们想要什么的部分信息。这意味着修改这个函数比较容易,不需要同时修改使用这个函数的代码。
1.1.4 实现
数据来源可以有很多形式。存取数据的文件主要有两类:人类可读文件和二进制文件。人类可读的文件的例子有:XML、Atom、YAML和Json。二进制文件的例子则有SQLite使用的.sq3的文件格式,及用于听音乐的.mp3的文件格式。
以下例子将关注两种流行的人类可读文件格式: XML和JSON。虽然人类可读文件解析起来通常比二进制文件更慢,但更易于数据交换、审查和修改。基于这种考虑,建议优先使用人类可读文件,除非有其他限制因素不允许使用这类格式(主要的限制包括性能不可接受以及专有的二 进制格式)。
我们将使用Python发行版自带的两个库( xml.etree.ElementTree和json)来处理XML 和JSON,如下所示。
import xml.etree.ElementTree as etree
import json
类JSONConnector解析JSON文件,通过parsed_data()方法以一个字典( dict)的形式 返回数据。修饰器property使parsed_data()显得更像一个常规的变量,而不是一个方法,如 下所示。
class JSONConnector:
def __init__(self, filepath):
qself.data = dict()
with open(filepath, mode='r', encoding='utf-8') as f:
qself.data = json.load(f)
类XMLConnector解析 XML 文件,通过parsed_data()方法以xml.etree.Element列表 的形式返回所有数据,如下所示。
class XMLConnector:
def __init__(self, filepath):
self.tree = etree.parse(filepath)
函数connection_factory是一个工厂方法,基于输入文件路径的扩展名返回一个 JSONConnector或XMLConnector的实例,如下所示。
def connector_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 ve:
print(ve)
return factory
函数main()演示如何使用工厂方法设计模式。第一部分是确认异常处理是否有效,如下 所示。
def main():
sqlite_factory = connect_to('data/person.sq3')
接下来的部分演示如何使用工厂方法处理XML文件。 XPath用于查找所有包含姓( last name) 为Liar的person元素。对于每个匹配到的元素,展示其基本的姓名和电话号码信息,如下所示。
xml_factory = connect_to('data/person.xml')
xml_data = xml_factory.parsed_data()
liars = xml_data.findall(".//{person}[{lastName}='{}']".format('Liar'))
print('found: {} persons'.format(len(liars)))
for liar in liars:
print('first name: {}'.format(liar.find('firstName').text))
print('last name: {}'.format(liar.find('lastName').text))
[print('phone number ({}):'.format(p.attrib['type']),
p.text) for p in liar.find('phoneNumbers')]
最后一部分演示如何使用工厂方法处理JSON文件。这里没有模式匹配,因此所有甜甜圈的 name、 price和topping如下所示。
json_factory = connect_to('data/donut.json')
json_data = json_factory.parsed_data
print('found: {} donuts'.format(len(json_data)))
for donut in json_data:
print('name: {}'.format(donut['name']))
print('price: ${}'.format(donut['ppu']))
[print('topping: {} {}'.format(t['id'], t['type'])) for t
in donut['topping']]
为便于整体理解,下面给出工厂方法实现( factory_method.py)的完整代码。
1 import xml.etree.ElementTree as etree 2 import json 3 class JSONConnector: 4 def __init__(self, filepath): 5 self.data = dict() 6 with open(filepath, mode='r', encoding='utf-8') as f: 7 self.data = json.load(f) 8 @property 9 def parsed_data(self): 10 return self.data 11 class XMLConnector: 12 def __init__(self, filepath): 13 self.tree = etree.parse(filepath) 14 @property 15 def parsed_data(self): 16 return self.tree 17 def connection_factory(filepath): 18 if filepath.endswith('json'): 19 connector = JSONConnector 20 elif filepath.endswith('xml'): 21 connector = XMLConnector 22 else: 23 raise ValueError('Cannot connect to {}'.format(filepath)) 24 return connector(filepath) 25 def connect_to(filepath): 26 factory = None 27 try: 28 factory = connection_factory(filepath) 29 except ValueError as ve: 30 print(ve) 31 return factory 32 def main(): 33 sqlite_factory = connect_to('data/person.sq3') 34 print() 35 xml_factory = connect_to('data/person.xml') 36 xml_data = xml_factory.parsed_data 37 liars = xml_data.findall(".//{}[{}='{}']".format('person', 38 'lastName', 'Liar')) 39 print('found: {} persons'.format(len(liars))) 40 for liar in liars: 41 print('first name: {}'.format(liar.find('firstName').text)) 42 print('last name: {}'.format(liar.find('lastName').text)) 43 [print('phone number ({})'.format(p.attrib['type']), 44 p.text) for p in liar.find('phoneNumbers')] 45 print() 46 json_factory = connect_to('data/donut.json') 47 json_data = json_factory.parsed_data 48 print('found: {} donuts'.format(len(json_data))) 49 for donut in json_data: 50 print('name: {}'.format(donut['name'])) 51 print('price: ${}'.format(donut['ppu'])) 52 [print('topping: {} {}'.format(t['id'], t['type'])) for t in donut['topping']] 53 if __name__ == '__main__': 54 main()
该程序的输出如下所示。
>>> python3 factory_method.py
Cannot connect to data/person.sq3
found: 2 persons
first name: Jimy
last name: Liar
phone number (home): 212 555-1234
first name: Patty
last name: Liar
phone number (home): 212 555-1234
phone number (mobile): 001 452-8819
found: 3 donuts
name: Cake
price: $0.55
topping: 5001 None
topping: 5002 Glazed
topping: 5005 Sugar
topping: 5007 Powdered Sugar
topping: 5006 Chocolate with Sprinkles
topping: 5003 Chocolate
topping: 5004 Maple
name: Raised
price: $0.55
topping: 5001 None
topping: 5002 Glazed
topping: 5005 Sugar
topping: 5003 Chocolate
topping: 5004 Maple
name: Old Fashioned
price: $0.55
topping: 5001 None
topping: 5002 Glazed
topping: 5003 Chocolate
topping: 5004 Maple
注意,虽然JSONConnector和XMLConnector拥有相同的接口,但是对于parsed_data()返回的数据并不是以统一的方式进行处理。对于每个连接器,需使用不同的Python代码来处理。若能对所有连接器应用相同的代码当然最好,但是在多数时候这是不现实的,除非对数据使用某种共同的映射,这种映射通常是由外部数据提供者提供。
1.2 抽象工厂
抽象工厂设计模式是抽象方法的一种泛化。概括来说,一个抽象工厂是(逻辑上的)一组工 厂方法,其中的每个工厂方法负责产生不同种类的对象 。
1.2.1 现实生活中的例子
汽车制造业应用了抽象工厂的思想。冲压不同汽车模型的部件(车门、仪表盘、车篷、挡泥板及反光镜等)所使用的机件是相同的。机件装配起来的模型随时可配置,且易于改变。从下图我们能看到汽车制造业抽象工厂的一个例子。
1.2.2 软件的例子
程序包django_factory是一个用于在测试中创建Django模型的抽象工厂实现,可用来为支持测 试专有属性的模型创建实例。
1.2.3 应用案例
因为抽象工厂模式是工厂方法模式的一种泛化,所以它能提供相同的好处:让对象的创建更容易追踪;将对象创建与使用解耦;提供优化内存占用和应用性能的潜力。
这样会产生一个问题:我们怎么知道何时该使用工厂方法,何时又该使用抽象工厂?答案是,通常一开始时使用工厂方法,因为它更简单。如果后来发现应用需要许多工厂方法,那么将创建一系列对象的过程合并在一起更合理,从而最终引入抽象工厂。
抽象工厂有一个优点,在使用工厂方法时从用户视角通常是看不到的,那就是抽象工厂能够 通过改变激活的工厂方法动态地(运行时)改变应用行为。
1.2.4 实现
代码如下:
1 class Frog: 2 def __init__(self, name): 3 self.name = name 4 def __str__(self): 5 return self.name 6 def interact_with(self, obstacle): 7 print('{} the Frog encounters {} and {}!'.format(self,obstacle, obstacle.action())) 8 class Bug: 9 def __str__(self): 10 return 'a bug' 11 def action(self): 12 return 'eats it' 13 class FrogWorld: 14 def __init__(self, name): 15 print(self) 16 self.player_name = name 17 def __str__(self): 18 return '\n\n\t------ Frog World -------' 19 def make_character(self): 20 return Frog(self.player_name) 21 def make_obstacle(self): 22 return Bug() 23 class Wizard: 24 def __init__(self, name): 25 self.name = name 26 def __str__(self): 27 return self.name 28 def interact_with(self, obstacle): 29 print('{} the Wizard battles against {} and {}!'.format(self, obstacle,obstacle.action())) 30 class Ork: 31 def __str__(self): 32 return 'an evil ork' 33 def action(self): 34 return 'kills it' 35 class WizardWorld: 36 def __init__(self, name): 37 print(self) 38 self.player_name = name 39 def __str__(self): 40 return '\n\n\t------ Wizard World -------' 41 def make_character(self): 42 return Wizard(self.player_name) 43 def make_obstacle(self): 44 return Ork() 45 class GameEnvironment: 46 def __init__(self, factory): 47 self.hero = factory.make_character() 48 self.obstacle = factory.make_obstacle() 49 def play(self): 50 self.hero.interact_with(self.obstacle) 51 def validate_age(name): 52 try: 53 age = input('Welcome {}. How old are you? '.format(name)) 54 age = int(age) 55 except ValueError as err: 56 print("Age {} is invalid, please try again...".format(age)) 57 return (False, age) 58 return (True, age) 59 def main(): 60 name = input("Hello. What's your name? ") 61 valid_input = False 62 while not valid_input: 63 valid_input, age = validate_age(name) 64 game = FrogWorld if age < 18 else WizardWorld 65 environment = GameEnvironment(game(name)) 66 environment.play() 67 if __name__ == '__main__': 68 main()
该程序的输出如下:
>>> python3 abstract_factory.py
Hello. What's your name? Nick
Welcome Nick. How old are you? 17
------ Frog World -------
Nick the Frog encounters a bug and eats it!
1.3 小结
工厂方法设计模式的实现是一个不属于任何类的单一函数,负责单一种类对象(一个形状、一个连接点或者其他对象)的创建。
抽象工厂设计模式的实现是同属于单个类的许多个工厂方法用于创建一系列种类的相关对 象(一辆车的部件、一个游戏的环境,或者其他对象)。