欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

创建型--工厂模式1

程序员文章站 2022-06-15 14:17:54
...

什么叫工厂方法模式


在工厂方法模式中,我们提供一个带参数的函数,依据参数的不同,返回不同的实例对象。工厂方法模式并不需要知道创建实例对象的具体细节。例如,我们现在有不同的文件,XML、Atom、YAML和 JSON,我们需要解析这些文件,利用 工厂方法模式 如何实现呢?如下图所示:

创建型--工厂模式1

我们可以一个 **工厂函数**,该函数的参数为 **文件路径**,该函数依据文件的后缀名返回能够处理该文件的实例对象,例如: - 如果输入文件为 `xxx.xml` 文件,则返回处理 `.xml` 文件的实例对象; - 如果输入文件为 `xxx.json` 文件,则返回处理 `.json` 文件的实例对象;

工厂方法模式的好处


工厂模式背后的思想是简化对象的创建。

  • 隐藏对象创建的细节: 在工厂设计模式中,客户端可以请求一个对象,无需知道对象来自哪里和创建细节;
  • 易于追踪创建的对象: 与客户端自己基于类实例化直接创建对象相比,基于一个中心化函数来实现,更易于追踪创建了哪些对象;
  • 使用与创建解耦: 可以实现创建对象的代码与使用对象的代码解耦,降低了代码维护的复杂度;
  • 优化性能以及内存: 工厂方法只有在实际需要的时候才会创建实例对象,从而提高了性能和使用率;

工厂方法模式如何使用


假设我们要解析一堆文件,这些文件有 xmljsonyaml等,我们利用工厂模式完成这一功能。


xml 和 json 文件的格式

我们存在两个文件:person.xmldonut.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

源码链接在这里;


参考资料


  1. <精通Python设计模式>;
相关标签: 工厂方法模式