基于Python的工厂模式学习总结

学习书籍精通Python设计模式

介绍

目的:简化对象的创建过程,在创建对象时无需关心内部逻辑与实现。

作用:与直接使用类实例化来创建对象相比,使用一个中心函数来创建对象更容易追踪。实现对象创建与使用的解耦,降低维护应用的复杂度。

工厂模式有两种形式:

  1. 工厂方法
  2. 抽象工厂

工厂方法

  • 基于单一的函数来处理对象创建任务
  • 传入一个参数,返回一个对象

Django框架的模型类使用的就是工厂方法:

foms就是一个工厂方法,传入CharFieldDateField就可以创建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}
 ]
<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> 

使用json和xml.etree来解析文件

python代码如下,其中 JSONDataExtractorXMLDataExtractor 为解析文件的类, @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()

图解

在这里插入图片描述

抽象工厂

  • 抽象工厂就是对许多工厂方法进行抽象、集合。
  • 比如一个汽车制造厂有许多生产线(门,车轮,发动机等等),每一个生产线就是一个工厂方法,你给他传递材料就返回给你所需的对象,整个生产线组合起来就是抽象工厂。

何时使用抽象工厂

当发现程序需要许多工厂方法,且将这些方法组合起来创建一系列对象是有意义的,那么就是用抽象工厂。

好处

抽象工厂可以使我们通过更改处于激活状态的工厂方法,动态的(在运行时)修改应用程序的行为。

抽象工厂模式的实现

一个小游戏程序,根据用户输入的年龄来判断用户该玩哪款游戏。

  1. 为Frog World游戏定义FrogBug
  2. 添加 FrogWorld 类,在其中使用FrogBug
  3. 为 Wizard game 游戏定义 WizardOrk
  4. 添加Wizard game 类,在其中使用WizardOrk
  5. 定义 GameEnvironment
  6. 添加 validate_age()函数
  7. 最后添加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()

图解
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一切如来心秘密

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值