精通Python设计模式第二版 第 1 章 工厂模式 学习笔记

工厂模式

工厂模式的思想意在简化对象的创建过程。调用者(客户端)不清楚对象的来源以及生成对象的方式,调用者只需要调用某个方法或者实例某个类 既可以获得想要对象。这些对象根据不同的生产需求而产生

两种形式

  • 工厂方法:一个方法(或者说函数)根据不同的输入参数返回不同的对象
  • 抽象工厂:一组用于创建一系列相关对象的工厂方法

工厂方法的实现

让我们想象一下这样一个需求,我们有一些数据文件需要分析,分别是XML和JSON。这两种文件需要不容的库解析数据,但是调用者不想关心,然后区分处理这两种数据。这种场景需求就可以使用工厂方法来解决,工厂方法会帮我们做好不同数据格式的数据解析。

让我们先看一下 需要解析的文件数据

movies.json

[{"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}
]

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> 

接下来,让我们看看如何实现这样一个工厂方法

1. 先找到能解析这两种数据的库

import json
import xml.etree.ElementTree as etree

2. 解析数据的方法

类JSONDataExtractor用于解析JSON文件,它有parsed_data()方法,返回一个包含所有数据的字典。
类XMLDataExtractor则用于解析XML文件

class JSONDataExtractor:"""JSONDataExtractor : 用于解析JSON文件Attributes:"""def __init__(self, filepath):self.data = dict()  # 包含所有数据的字典with open(filepath, 'r', encoding='utf8') as f:self.data = json.load(f)@propertydef parsed_data(self):return self.dataclass XMLDataExtractor():"""XMLDataExtractor : 用于解析XML文件Attributes:"""def __init__(self, filepath):self.tree = etree.parse(filepath)@propertydef parsed_data(self):return self.tree

3. 工厂方法

根据输入文件的拓展名,返回一个JSONDataExtractor或XMLDataExtractor的实例

def dataextraction_factory(filepath):'''工厂方法 根据输入文件的拓展名,返回一个JSONDataExtractor或XMLDataExtractor的实例:param filepath::return:'''if filepath.endswith('json'):extractor = JSONDataExtractorelif filepath.endswith('xml'):extractor = XMLDataExtractorelse:raise ValueError('Cannot extract data from {}'.format(filepath))return extractor(filepath)

4. 最后添加一些异常处理机制

def extract_data_from(filepath):'''dataextraction_factory() 方法的装饰器。它增加了异常处理机制:param filepath::return:'''factory_obj = Nonetry:factory_obj = dataextraction_factory(filepath)except ValueError as e:print(e)return factory_obj

5. 工厂函数的使用

def main():# 展示如果使用 工厂方法处理JSON文件json_factory = extract_data_from('data/movies.json')json_data = json_factory.parsed_dataprint(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文件xml_factory = extract_data_from('data/person.xml')xml_data = xml_factory.parsed_dataliars = xml_data.findall(f".//person[lastName='Liar']")print(f'found: {len(liars)} persons')for liar in liars:firstname = liar.find('firstName').textprint(f'first name: {firstname}')lastname = liar.find('lastName').textprint(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()

获得的打印如下

Found: 9 movies
Title: After Dark in Central Park
Year: 1900Title: Boarding School Girls' Pajama Parade
Year: 1900Title: Buffalo Bill's Wild West Parad
Year: 1900Title: Caught
Year: 1900Title: Clowns Spinning Hats
Year: 1900Title: Capture of Boer Battery by British
Year: 1900
Director: James H. White
Genre: Short documentaryTitle: The Enchanted Drawing
Year: 1900
Director: J. Stuart BlacktonTitle: Family Troubles
Year: 1900Title: Feeding Sea Lions
Year: 1900found: 2 persons
first name: Jimy
last name: Liar
phone number (home): 212 555-1234first name: Patty
last name: Liar
phone number (home): 212 555-1234
phone number (mobile): 001 452-8819

工厂方法完整代码

factory_method.py

import json
import xml.etree.ElementTree as etreeclass JSONDataExtractor:"""JSONDataExtractor : 用于解析JSON文件Attributes:"""def __init__(self, filepath):self.data = dict()  # 包含所有数据的字典with open(filepath, 'r', encoding='utf8') as f:self.data = json.load(f)@propertydef parsed_data(self):return self.dataclass XMLDataExtractor():"""XMLDataExtractor : 用于解析XML文件Attributes:"""def __init__(self, filepath):self.tree = etree.parse(filepath)@propertydef parsed_data(self):return self.treedef dataextraction_factory(filepath):'''工厂方法 根据输入文件的拓展名,返回一个JSONDataExtractor或XMLDataExtractor的实例:param filepath::return:'''if filepath.endswith('json'):extractor = JSONDataExtractorelif filepath.endswith('xml'):extractor = XMLDataExtractorelse:raise ValueError('Cannot extract data from {}'.format(filepath))return extractor(filepath)def extract_data_from(filepath):'''dataextraction_factory() 方法的装饰器。它增加了异常处理机制:param filepath::return:'''factory_obj = Nonetry:factory_obj = dataextraction_factory(filepath)except ValueError as e:print(e)return factory_objdef main():# sqlite_factory = extract_data_from('data/person.sq3')# print()# 展示如果使用 工厂方法处理JSON文件json_factory = extract_data_from('data/movies.json')json_data = json_factory.parsed_dataprint(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文件xml_factory = extract_data_from('data/person.xml')xml_data = xml_factory.parsed_dataliars = xml_data.findall(f".//person[lastName='Liar']")print(f'found: {len(liars)} persons')for liar in liars:firstname = liar.find('firstName').textprint(f'first name: {firstname}')lastname = liar.find('lastName').textprint(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()

抽象工厂的实现

假设我们正在创建一个游戏,或者想将一个迷你游戏作为应用程序的一部分来取悦用户。我们希望至少包括两款游戏:一款儿童游戏,一款成人游戏。我们将根据用户的输入,在运行时决定创建和启动哪款游戏

1. 儿童游戏的创建

男主角是一只喜欢吃虫子的青蛙,每一个主角都需要一个好的名字

class Frog():"""Frog :Attributes:"""def __init__(self, name):self.name = namedef __str__(self):return self.namedef interact_with(self, obstacle):'''用于描述 青蛙和障碍物之间的交互:param obstacle::return:'''act = obstacle.action()msg = f'{self} the Frog encounters {obstacle} and {act} !'print(msg)class Bug():"""Bug :Attributes:"""def __str__(self):return 'a bug'def action(self):'''青蛙只支持一种行为:吃掉虫子'''return 'eats it'

2. 儿童游戏的抽象工厂

它的主要任务是创建游戏中的主角与障碍物。保持创建方法的独立性以及名称的通用性

class FrogWorld():"""FrogWorld : 抽象工厂。它的主要任务是创建游戏中的主角与障碍物Attributes:"""def __init__(self, name):print(self)self.player_name = namedef __str__(self):return '\n\n\t------ Frog World ------'def make_character(self):return Frog(self.player_name)def make_obstacle(self):return Bug()

3. 成人游戏的创建

跟儿童游戏的区别是,这是一个巫师打兽人的游戏

class Wizard():"""Wizard :Attributes:"""def __init__(self, name):self.name = namedef __str__(self):return self.namedef interact_with(self, obstacle):act = obstacle.action()msg = f'{self} the Wizard battles against {obstacle} and {act}!'print(msg)class Ork():"""Ork :Attributes:"""def __str__(self):return 'an evil ork'def action(self):return 'kills it'

4. 成人游戏的抽象工厂

情况与儿童游戏类似

class WizardWorld():"""WizardWorld :Attributes:"""def __init__(self, name):print(self)self.player_name = namedef __str__(self):return '\n\n\t------ Wizard World ------'def make_character(self):return Wizard(self.player_name)def make_obstacle(self):return Ork()

5. 游戏的主入口

接收一个工厂作为输入,并使用它来创建游戏世界

class GameEnvironment():"""GameEnvironment : 游戏的主入口。它接收一个工厂作为输入,并使用它来创建游戏世界Attributes:"""def __init__(self, facotry):self.hero = facotry.make_character()self.obstacle = facotry.make_obstacle()def play(self):'''初始化主角与障碍物之间的交互:return:'''self.hero.interact_with(self.obstacle)

6. 游戏创建逻辑

def validate_age(name):'''提示用户给出一个有效年龄。如果年龄无效,则返回一个首元素为False的元祖:param name::return:'''try:age = input(f'Welcome {name}. How old are you? \n')age = int(age)except ValueError as err:print(f'Age {age} is invalid, please try again...')return (False, age)return (True, age)

7. 游戏运行逻辑

def main():'''询问用户姓名和年龄,并根据用户的年龄决定应该玩哪款游戏:return:'''name = input("Hello, What's your name? \n")valid_input = Falsewhile not valid_input:valid_input, age = validate_age(name)game = FrogWorld if age < 18 else WizardWorldenvironment = GameEnvironment(game(name))environment.play()if __name__ == '__main__':main()

获取打印如下

Hello, What's your name?
coco
Welcome coco. How old are you?
19------ Wizard World ------
coco the Wizard battles against an evil ork and kills it!

抽象工厂完整代码

class Frog():"""Frog :Attributes:"""def __init__(self, name):self.name = namedef __str__(self):return self.namedef interact_with(self, obstacle):'''用于描述 青蛙和障碍物之间的交互:param obstacle::return:'''act = obstacle.action()msg = f'{self} the Frog encounters {obstacle} and {act} !'print(msg)class Bug():"""Bug :Attributes:"""def __str__(self):return 'a bug'def action(self):'''青蛙只支持一种行为:吃掉虫子'''return 'eats it'class FrogWorld():"""FrogWorld : 抽象工厂。它的主要任务是创建游戏中的主角与障碍物Attributes:"""def __init__(self, name):print(self)self.player_name = namedef __str__(self):return '\n\n\t------ Frog World ------'def make_character(self):return Frog(self.player_name)def make_obstacle(self):return Bug()class Wizard():"""Wizard :Attributes:"""def __init__(self, name):self.name = namedef __str__(self):return self.namedef interact_with(self, obstacle):act = obstacle.action()msg = f'{self} the Wizard battles against {obstacle} and {act}!'print(msg)class Ork():"""Ork :Attributes:"""def __str__(self):return 'an evil ork'def action(self):return 'kills it'class WizardWorld():"""WizardWorld :Attributes:"""def __init__(self, name):print(self)self.player_name = namedef __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():"""GameEnvironment : 游戏的主入口。它接收一个工厂作为输入,并使用它来创建游戏世界Attributes:"""def __init__(self, facotry):self.hero = facotry.make_character()self.obstacle = facotry.make_obstacle()def play(self):'''初始化主角与障碍物之间的交互:return:'''self.hero.interact_with(self.obstacle)def validate_age(name):'''提示用户给出一个有效年龄。如果年龄无效,则返回一个首元素为False的元祖:param name::return:'''try:age = input(f'Welcome {name}. How old are you? \n')age = int(age)except ValueError as err:print(f'Age {age} is invalid, please try again...')return (False, age)return (True, age)def main():'''询问用户姓名和年龄,并根据用户的年龄决定应该玩哪款游戏:return:'''name = input("Hello, What's your name? \n")valid_input = Falsewhile not valid_input:valid_input, age = validate_age(name)game = FrogWorld if age < 18 else WizardWorldenvironment = GameEnvironment(game(name))environment.play()if __name__ == '__main__':main()

Python 设计模式 - 工厂模式相关推荐

  1. Python设计模式-策略模式

    Python设计模式-策略模式 代码基于3.5.2,代码如下; #coding:utf-8 #策略模式class sendInterface():def send(self,value):raise ...

  2. Python设计模式-建造者模式

    Python设计模式-建造者模式 代码基于3.5.2,代码如下; #coding:utf-8 #建造者模式 class Burger():name = ""price = 0.0d ...

  3. Python设计模式-状态模式

    Python设计模式-状态模式 代码基于3.5.2,代码如下; #coding:utf-8 #状态模式class state():def writeProgram(self,work):raise N ...

  4. Python设计模式-备忘录模式

    Python设计模式-备忘录模式 代码基于3.5.2,代码如下; #coding:utf-8 #备忘录模式 import randomclass gameCharacter():vitality = ...

  5. Python设计模式-解释器模式

    Python设计模式-解释器模式 代码基于3.5.2,代码如下; #coding:utf-8 #解释器模式class PlayContext():play_text = Noneclass Expre ...

  6. Python设计模式-命令模式

    Python设计模式-命令模式 代码基于3.5.2,代码如下; #coding:utf-8 #命令模式class barbecuer():def bakeButton(self):print(&quo ...

  7. Python设计模式-外观模式

    Python设计模式-外观模式 代码基于3.5.2,代码如下; #coding:utf-8 # 外观模式class AlarmSensor:def run(self):print("Alar ...

  8. Python设计模式-桥接模式

    Python设计模式-桥接模式 基于Python3.5.2,代码如下 #coding:utf-8class Shape():name = ""param = "" ...

  9. Python设计模式-代理模式

    Python设计模式-代理模式 基于Python3.5.2,代码如下 #coding:utf-8info_struct = dict() info_struct["addr"] = ...

最新文章

  1. 小程序不支持wx.request同步请求解决方法
  2. 华为鸿蒙系统手机销量,两个品牌助力华为新生,但最终会是谁拯救谁
  3. ARM函数调用时参数传递规则
  4. 整理的常用JAVA开源库简介
  5. 不擅演讲的马化腾在 08 年讲了什么?
  6. python字典由什么组成_在Python中,将由关键字对组成的列表添加到字典中最简单的方法是什么?...
  7. 【HDOJ2087】剪花布条(KMP)
  8. 签证计算机专业敏感吗,签证中最常见被check的14个敏感专业
  9. 电力系统卫星时钟同步工作的重要性
  10. 从零开始一起学习SLAM | 学习SLAM到底需要学什么?
  11. B样条曲线(B-spline Curves)
  12. linux下查看设备的接口,linux 查看sdio接口有哪些设备?
  13. 四叶草剧场服务器维修价格,四叶草剧场服务器介绍 服务器选择攻略
  14. postman全方位讲解(有空看下)
  15. 电商、数字化下的中国书店
  16. 基于时间序列分析方法的零售业快消品销量预测研究
  17. Error: errCode: -501000 | errMsg: Environment create at tencentcloud cannot access from wx-miniapp
  18. 什么是 “零” 拷贝?
  19. emacs官方中文手册
  20. Hive的安装与配置

热门文章

  1. python中单位转换_Python中的单位转换
  2. 寺冈labelnet使用说明_寺冈秤常用操作手册
  3. mysql_5.7_windows安装步骤
  4. BKDRHash 算法java实现
  5. django讲解 模型一
  6. ArcGIS|一文彻底搞懂GIS图斑编号
  7. 使用搜索引擎在指定网站内搜索指定内容
  8. 二分链路预测(数据挖掘实验一)
  9. 2011级-csdn-java-张侃— JavaWEB开发-国际化(二)
  10. 50多岁学python_学习 Python 看这一篇就够了!