创建型--工厂模式1
程序员文章站
2022-06-15 14:17:54
...
什么叫工厂方法模式
在工厂方法模式中,我们提供一个带参数的函数,依据参数的不同,返回不同的实例对象。工厂方法模式并不需要知道创建实例对象的具体细节。例如,我们现在有不同的文件,XML、Atom、YAML和 JSON,我们需要解析这些文件,利用 工厂方法模式 如何实现呢?如下图所示:
工厂方法模式的好处
工厂模式背后的思想是简化对象的创建。
- 隐藏对象创建的细节: 在工厂设计模式中,客户端可以请求一个对象,无需知道对象来自哪里和创建细节;
- 易于追踪创建的对象: 与客户端自己基于类实例化直接创建对象相比,基于一个中心化函数来实现,更易于追踪创建了哪些对象;
- 使用与创建解耦: 可以实现创建对象的代码与使用对象的代码解耦,降低了代码维护的复杂度;
- 优化性能以及内存: 工厂方法只有在实际需要的时候才会创建实例对象,从而提高了性能和使用率;
工厂方法模式如何使用
假设我们要解析一堆文件,这些文件有 xml
、json
、yaml
等,我们利用工厂模式完成这一功能。
xml 和 json 文件的格式
我们存在两个文件:person.xml
和 donut.json
文件,其中:
-
person.xml
:包含个人信息:firstName、lastName、age、address、phoneNumber、gender
;具体信息如下所示:
// data/person.xml
<persons>
<person>
<firstName>John</firstName>
<lastName>Smith</lastName>
<age>25</age>
<address>
<streetAddress>21 2nd Street</streetAddress>
<city>New York</city>
<state>NY</state>
<postalCode>10021</postalCode>
</address>
<phoneNumbers>
<phoneNumber type="home">212 555-1234</phoneNumber>
<phoneNumber type="fax">646 555-4567</phoneNumber>
</phoneNumbers>
<gender>
<type>male</type>
</gender>
</person>
<person>
<firstName>Jimy</firstName>
<lastName>Liar</lastName>
<age>19</age>
<address>
<streetAddress>18 2nd Street</streetAddress>
<city>New York</city>
<state>NY</state>
<postalCode>10021</postalCode>
</address>
<phoneNumbers>
<phoneNumber type="home">212 555-1234</phoneNumber>
</phoneNumbers>
<gender>
<type>male</type>
</gender>
</person>
<person>
<firstName>Patty</firstName>
<lastName>Liar</lastName>
<age>20</age>
<address>
<streetAddress>18 2nd Street</streetAddress>
<city>New York</city>
<state>NY</state>
<postalCode>10021</postalCode>
</address>
<phoneNumbers>
<phoneNumber type="home">212 555-1234</phoneNumber>
<phoneNumber type="mobile">001 452-8819</phoneNumber>
</phoneNumbers>
<gender>
<type>female</type>
</gender>
</person>
</persons>
-
donut.json
:包含甜甜圈的信息:id、type、name、ppu、batters、topping
等,具体格式如下:
# data/donut.json
[
{
"id": "0001",
"type": "donut",
"name": "Cake",
"ppu": 0.55,
"batters": {
"batter": [
{
"id": "1001",
"type": "Regular"
},
{
"id": "1002",
"type": "Chocolate"
},
{
"id": "1003",
"type": "Blueberry"
},
{
"id": "1004",
"type": "Devil's Food"
}
]
},
"topping": [
{
"id": "5001",
"type": "None"
},
{
"id": "5002",
"type": "Glazed"
},
{
"id": "5005",
"type": "Sugar"
},
{
"id": "5007",
"type": "Powdered Sugar"
},
{
"id": "5006",
"type": "Chocolate with Sprinkles"
},
{
"id": "5003",
"type": "Chocolate"
},
{
"id": "5004",
"type": "Maple"
}
]
},
{
"id": "0002",
"type": "donut",
"name": "Raised",
"ppu": 0.55,
"batters": {
"batter": [
{
"id": "1001",
"type": "Regular"
}
]
},
"topping": [
{
"id": "5001",
"type": "None"
},
{
"id": "5002",
"type": "Glazed"
},
{
"id": "5005",
"type": "Sugar"
},
{
"id": "5003",
"type": "Chocolate"
},
{
"id": "5004",
"type": "Maple"
}
]
},
{
"id": "0003",
"type": "donut",
"name": "Old Fashioned",
"ppu": 0.55,
"batters": {
"batter": [
{
"id": "1001",
"type": "Regular"
},
{
"id": "1002",
"type": "Chocolate"
}
]
},
"topping": [
{
"id": "5001",
"type": "None"
},
{
"id": "5002",
"type": "Glazed"
},
{
"id": "5003",
"type": "Chocolate"
},
{
"id": "5004",
"type": "Maple"
}
]
}
]
涉及到的头文件
我们将使用Python发行版自带的两个库来处理XML和JSON,如下所示:
import xml.etree.ElementTree as etree
import json
相关的类
为每一个类型的文件建立一个解析类:
-
JSONConnector
用来解析 JSON 文件; -
XMLConnector
用来解析 XML 文件;
# 处理JSON文件类
class JSONConnector(object):
def __init__(self, filepath):
self.data = dict()
with open(filepath, mode='r', encoding='utf-8') as file:
self.data = json.load(file)
@property
def parsed_data(self):
return self.data
# 处理XML文件的类
class XMLConnector(object):
def __init__(self, filepath):
self.tree = etree.parse(filepath)
@property
def parsed_data(self):
return self.tree
上述代码中:
- 在 JSON 类中,通过
parsed_data()
方法以一个字典(dict)的形式返回数据; - 在 XML 类中,通过
parsed_data()
方法以xml.etree.Element
列表的形式返回所有数据; - 修饰器
property
使得parsed_data()
可以向常规的变量一样使用;
建立工厂函数
工厂函数以文件路径名为参数,返回解析相应文件的实例对象,代码如下所示:
# 工厂方法:基于文件扩展名, 返回一个JSONConnector或XMLConnector的实例
def factory_mode(filepath):
if filepath.endswith('json'):
connector = JSONConnector
elif filepath.endswith('xml'):
connector = XMLConnector
else:
raise ValueError('Cannot connect to {}'.format(filepath))
return connector(filepath)
# 对connector进行包装
def connect_to(filepath):
factory = None
try:
factory = factory_mode(filepath)
except ValueError as ve:
print(ve)
return factory
上述代码中,connect_to()
是对 factory_mode()
的包装,添加了异常处理功能。
演示工厂方法模式
演示代码如下所示:
def main():
# 确认异常处理是否有效
sqlite_factory = connect_to('data/person.sq3')
print()
# 解析xml文件
xml_factory = connect_to('data/person.xml')
xml_data = xml_factory.parsed_data
liars = xml_data.findall(".//{}[{}='{}']".format('person', 'lastName', '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')]
print()
# 解析json文件
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']]
if __name__ == '__main__':
main()
结果如下所示:
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
源码链接在这里;
参考资料
- <精通Python设计模式>;