学习书籍精通Python设计模式
目的:简化对象的创建过程,在创建对象时无需关心内部逻辑与实现。
作用:与直接使用类实例化来创建对象相比,使用一个中心函数来创建对象更容易追踪。实现对象创建与使用的解耦,降低维护应用的复杂度。
工厂模式有两种形式:
Django框架的模型类使用的就是工厂方法:
foms
就是一个工厂方法,传入CharField
和DateField
就可以创建name
对象和birth_data
对象
from django import forms
class PersonForm(forms.Form):
name = forms.CharField(max_length=100)
birth_data = forms.DateField(required=False)
何时使用:如果创建对象的代码分布在许多不同的地方,导致难以追踪,这时候就可以使用工厂方法来重构。
以读取json文件和xml文件来举例,json和xml文件格式如下:
[
{"title":"After Dark in Central Park",
"year":1900,
"director":null, "cast":null, "genre":null},
{"title":"Boarding School Girls' Pajama Parade",
"year":1900,
"director":null, "cast":null, "genre":null},
{"title":"Buffalo Bill's Wild West Parad",
"year":1900,
"director":null, "cast":null, "genre":null},
{"title":"Caught",
"year":1900,
"director":null, "cast":null, "genre":null},
{"title":"Clowns Spinning Hats",
"year":1900,
"director":null, "cast":null, "genre":null},
{"title":"Capture of Boer Battery by British",
"year":1900,
"director":"James H. White", "cast":null, "genre":"Short documentary"},
{"title":"The Enchanted Drawing",
"year":1900,
"director":"J. Stuart Blackton", "cast":null,"genre":null},
{"title":"Family Troubles",
"year":1900,
"director":null, "cast":null, "genre":null},
{"title":"Feeding Sea Lions",
"year":1900,
"director":null, "cast":"Paul Boyton", "genre":null}
]
John
Smith
25
21 2nd Street
New York
NY
10021
212 555-1234
646 555-4567
male
Jimy
Liar
19
18 2nd Street
New York
NY
10021
212 555-1234
male
Patty
Liar
20
18 2nd Street
New York
NY
10021
212 555-1234
001 452-8819
female
使用json和xml.etree来解析文件
python代码如下,其中 JSONDataExtractor
, XMLDataExtractor
为解析文件的类, @property
装饰器是将类中的方法当做类的属性, dataextraction_factory
为工厂方法, extract_data_from
是对工厂方法的异常处理的装饰。在主函数中调用装饰过后的工厂方法就可以获取对应的对象,使用对象的属性和方法。
import json
import xml.etree.ElementTree as etree
class JSONDataExtractor:
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 XMLDataExtractor:
def __init__(self, filepath):
self.tree = etree.parse(filepath)
@property
def parsed_data(self):
return self.tree
def dataextraction_factory(filepath):
if filepath.endswith('json'):
extractor = JSONDataExtractor
elif filepath.endswith('xml'):
extractor = XMLDataExtractor
else:
raise ValueError('Cannot extract data from {}'.format(filepath))
return extractor(filepath)
def extract_data_from(filepath):
factory_obj = None
try:
factory_obj = dataextraction_factory(filepath)
except ValueError as e:
print(e)
return factory_obj
def main():
sqlite_factory = extract_data_from('data/person.sq3')
print()
json_factory = extract_data_from('data/movies.json')
json_data = json_factory.parsed_data
print(f'Found: {len(json_data)} movies')
for movie in json_data:
print(f"Title: {movie['title']}")
year = movie['year']
if year:
print(f"Year: {year}")
director = movie['director']
if director:
print(f"Director: {director}")
genre = movie['genre']
if genre:
print(f"Genre: {genre}")
print()
xml_factory = extract_data_from('data/person.xml')
xml_data = xml_factory.parsed_data
liars = xml_data.findall(f".//person[lastName='Liar']")
print(f'found: {len(liars)} persons')
for liar in liars:
firstname = liar.find('firstName').text
print(f'first name: {firstname}')
lastname = liar.find('lastName').text
print(f'last name: {lastname}')
[print(f"phone number ({p.attrib['type']}):", p.text)
for p in liar.find('phoneNumbers')]
print()
print()
if __name__ == '__main__':
main()
图解
何时使用抽象工厂
当发现程序需要许多工厂方法,且将这些方法组合起来创建一系列对象是有意义的,那么就是用抽象工厂。
好处
抽象工厂可以使我们通过更改处于激活状态的工厂方法,动态的(在运行时)修改应用程序的行为。
抽象工厂模式的实现
一个小游戏程序,根据用户输入的年龄来判断用户该玩哪款游戏。
Frog
和Bug
类FrogWorld
类,在其中使用Frog
和Bug
类Wizard
和 Ork
类Wizard
和Ork
类GameEnvironment
类validate_age()
函数main()
函数代码如下:
# Frog game
class Frog:
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
def interact_with(self, obstacle):
act = obstacle.action()
msg = f'{self} the Frog encounters {obstacle} and {act}!'
print(msg)
class Bug:
def __str__(self):
return 'a bug'
def action(self):
return 'eats it'
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):
return Frog(self.player_name)
def make_obstacle(self):
return Bug()
# Wizard game
class Wizard:
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
def interact_with(self, obstacle):
act = obstacle.action()
msg = f'{self} the Wizard battles against {obstacle} and {act}!'
print(msg)
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()
# Game environment
class GameEnvironment:
def __init__(self, factory):
self.hero = factory.make_character()
self.obstacle = factory.make_obstacle()
def play(self):
self.hero.interact_with(self.obstacle)
def validate_age(name):
try:
age = input(f'Welcome {name}. How old are you? ')
age = int(age)
except ValueError as err:
print(f"Age {age} is invalid, please try again...")
return (False, 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 = FrogWorld if age < 18 else WizardWorld
environment = GameEnvironment(game(name))
environment.play()
if __name__ == '__main__':
main()