Python 设计模式 - 工厂模式

本文通过实例讲解了Python中工厂模式的两种实现形式——工厂方法和抽象工厂,以及它们在解析JSON和XML文件、游戏场景中的应用。理解并掌握如何根据文件类型动态创建解析器,以及如何根据不同用户需求选择游戏模式。
摘要由CSDN通过智能技术生成

精通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)

    @property
    def parsed_data(self):
        return self.data


class XMLDataExtractor():
    """
    XMLDataExtractor : 用于解析XML文件

    Attributes:

    """

    def __init__(self, filepath):
        self.tree = etree.parse(filepath)

    @property
    def parsed_data(self):
        return self.tree

3. 工厂方法

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

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

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

def extract_data_from(filepath):
    '''
    dataextraction_factory() 方法的装饰器。它增加了异常处理机制
    :param filepath:
    :return:
    '''
    factory_obj = None
    try:
        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_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文件
    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()

获得的打印如下

Found: 9 movies
Title: After Dark in Central Park
Year: 1900

Title: Boarding School Girls' Pajama Parade
Year: 1900

Title: Buffalo Bill's Wild West Parad
Year: 1900

Title: Caught
Year: 1900

Title: Clowns Spinning Hats
Year: 1900

Title: Capture of Boer Battery by British
Year: 1900
Director: James H. White
Genre: Short documentary

Title: The Enchanted Drawing
Year: 1900
Director: J. Stuart Blackton

Title: Family Troubles
Year: 1900

Title: Feeding Sea Lions
Year: 1900

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

工厂方法完整代码

factory_method.py

import json
import xml.etree.ElementTree as etree


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)

    @property
    def parsed_data(self):
        return self.data


class XMLDataExtractor():
    """
    XMLDataExtractor : 用于解析XML文件

    Attributes:

    """

    def __init__(self, filepath):
        self.tree = etree.parse(filepath)

    @property
    def parsed_data(self):
        return self.tree


def dataextraction_factory(filepath):
    '''
    工厂方法 根据输入文件的拓展名,返回一个JSONDataExtractor或XMLDataExtractor的实例
    :param filepath:
    :return:
    '''
    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):
    '''
    dataextraction_factory() 方法的装饰器。它增加了异常处理机制
    :param filepath:
    :return:
    '''
    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文件
    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文件
    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()

抽象工厂的实现

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

1. 儿童游戏的创建

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

class Frog():
    """
    Frog :

    Attributes:

    """

    def __init__(self, name):
        self.name = name

    def __str__(self):
        return self.name

    def 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 = 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()

3. 成人游戏的创建

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

class Wizard():
    """
    Wizard :

    Attributes:

    """

    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():
    """
    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 = 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()

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 = 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()

获取打印如下

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 = name

    def __str__(self):
        return self.name

    def 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 = 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()


class Wizard():
    """
    Wizard :

    Attributes:

    """

    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():
    """
    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 = 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():
    """
    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 = 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()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值