# 第 1 章 工厂模式

创建型设计模式处理对象创建相关的问题，目标是当直接创建对象不太方便时，提供更好的方式。

在工厂设计模式中，客户端可以请求一个对象，而无需知道这个对象来自哪里；也就是，使用哪个类来生成这个对象。工厂背后的思想是简化对象的创建。

工厂通常有两种形式，一种是工厂方法（Factory Method），它是一个方法，对不同的输入参数返回不同的对象；第二种是抽象工厂，它是一组用于创建一系列相关事物对象的工厂方法。

## 1.1 工厂方法

Django 框架使用工厂方法模式来创建表单字段。Django 的 forms 模块支持不同种类字段（CharField、EmailField）的创建和定制（`max_length`、`required`）。

如果因为应用创建对象的代码分布在多个不同的地方，而不是仅在一个函数/方法中，你发现没法跟踪这些对象，那么应该考虑使用工厂方法模式。

创建多个工厂方法也完全没有问题，实践中通常也这么做，对相似的对象创建进行逻辑分组，每个工厂方法负责一个分组。

若需要将对象的创建和使用解耦，工厂方法也能派上用场。

工厂方法可以在必要时创建新的对象，从而提高性能和内存使用率。若直接实例化类来创建对象， 那么每次创建新对象就需要分配额外的内存（除非这个类内部使用了缓存，一般情况下不会这样）。

人类可读文件的例子有：XML、Atom、YAML 和 JSON。

建议优先使用人类可读文件，除非有其他限制因素不允许使用这类格式（主要的限制包括性能不可接受以及专有的二进制格式）。

### DEMO

```python
import xml.etree.ElementTree as etree
import json

# 负责 JSON 解析
class JSONConnector:

    def __init__(self, filepath):
        self.data = dict()
        with open(filepath, mode='r', encoding='utf-8') as f:
            self.data = json.load(f)

    # 使用 property 修饰器使其看上去像是一个变量而不是函数
    @property
    def parsed_data(self):
        return self.data

# 负责 XML 解析
class XMLConnector:

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

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

# 工厂方法
def connection_factory(filepath):
    if filepath.endswith('json'):
        connector = JSONConnector
    elif filepath.endswith('xml'):
        connector = XMLConnector
    else:
        raise ValueError('Cannot connect to {}'.format(filepath))
    return connector(filepath)

# 增加异常处理的工厂方法
def connect_to(filepath):
    factory = None
    try:
        factory = connection_factory(filepath)
    except ValueError as ve:
        print(ve)
    return factory


def main():
    # 测试异常
    sqlite_factory = connect_to('data/person.sq3')

    # 测试 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)))

    # 测试 JSON 解析
    json_factory = connect_to('data/donut.json')
    json_data = json_factory.parsed_data
    print('found: {} donuts'.format(len(json_data)))

if __name__ == '__main__':
    main()
```

主要关注：`connection_factory` 这个工厂方法以及增加异常处理的工厂方法 `connect_to`。

## 1.2 抽象工厂

一个抽象工厂是（逻辑上的）一组工厂方法，其中的每个工厂方法负责产生不同种类的对象。

程序包 `django_factory` 是一个用于在测试中创建 Django 模型的抽象工厂实现，可用来为支持测试专有属性的模型创建实例。

抽象工厂模式是工厂方法模式的一种泛化，所以它能提供相同的好处。

我们怎么知道何时该使用工厂方法，何时又该使用抽象工厂？答案是， 通常一开始时使用工厂方法，因为它更简单。如果后来发现应用需要许多工厂方法，那么将创建一系列对象的过程合并在一起更合理，从而最终引入抽象工厂。

抽象工厂能够通过改变激活的工厂方法动态地（运行时）改变应用行为。

### DEMO

```python
# 类别一、青蛙
class Frog:
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return self.name
    def interact_with(self, obstacle):
        print('{} the Frog encounters {} and {}!'.format(self,
                                                         obstacle, obstacle.action()))

# 类别一、虫子
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()

# 类别二、男巫
class Wizard:
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return self.name
    def interact_with(self, obstacle):
        print('{} the Wizard battles against {} and {}!'.format(self, obstacle, obstacle.action()))

# 类别二、兽人
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()

# 抽象工厂方法
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):
    pass


def main():
    # 一堆参数设置
    environment = GameEnvironment(game(name))
    environment.play()

if __name__ == '__main__':
    main()
```

主要关注的是，类别一和二都得对不同种类的对象进行实例化，于是封装抽象工厂模式，使得两种类别都可以凭这个抽象工厂方法创建各自的对象。

## 1.3 小结

两种模式都可以用于以下几种场景：(a)想要追踪对象的创建时，(b)想要将对象的创建与使用解耦时，(c)想要优化应用的性能和资源占用时。

工厂方法设计模式的实现是一个不属于任何类的单一函数，负责单一种类对象（一个形状、 一个连接点或者其他对象）的创建。

抽象工厂设计模式的实现是同属于单个类的许多个工厂方法用于创建一系列种类的相关对象（一辆车的部件、一个游戏的环境，或者其他对象）。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://l1nwatch.gitbook.io/python-design-mode/di-yi-bu-fen-chuang-jian-xing-mo-shi/di-1-zhang-gong-chang-mo-shi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
