創建型--工廠模式1

什麼叫工廠方法模式


在工廠方法模式中,我們提供一個帶參數的函數,依據參數的不同,返回不同的實例對象。工廠方法模式並不需要知道創建實例對象的具體細節。例如,我們現在有不同的文件,XML、Atom、YAML和 JSON,我們需要解析這些文件,利用 工廠方法模式 如何實現呢?如下圖所示:

在這裏插入圖片描述

我們可以一個 **工廠函數**,該函數的參數爲 **文件路徑**,該函數依據文件的後綴名返回能夠處理該文件的實例對象,例如: - 如果輸入文件爲 `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設計模式>;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章