Fork me on GitHub

Python设计模式——工厂模式

阅读《精通Python设计模式》的笔记,介绍了工厂模式的思想、优势和应用场景。并做了两个简单的实现。

In [ ]:
print('python3')

软件工程中,设计模式是指软件设计问题的推荐方案。设计模式一般是描述如何组织代码和使用最佳实践来解决常用的设计问题。设计模式是在已有的方案上发现更好的方案(而不是全新发明)。设计模式不是万能的,仅当代码存在臭味、难以扩展维护时,才有使用的必要。随处使用设计模式和过早优化一样,都是误入歧途。

参考书籍《精通Python设计模式》【荷】Sakis Kasampalis
书籍配套代码:https://github.com/youngsterxyf/mpdp-code

创建型模式

工厂模式

工厂背后的思想就是简化对象的创建。与客户端自己基于类实例化直接创建对象相比,基于一个中心化函数来实现,更易于追踪创建了哪些对象。通过将创建对象的代码和使用对象的代码解耦,工厂能够显著降低应用维护的复杂度。
工厂通常两种形式:

  • 工厂方法:对不同输入参数返回不同对象的函数
  • 抽象工厂:一组用于创建一系列相关事物对象的工厂方法

工厂方法

在单个函数中,传入一个参数(提供信息表示想要什么),但并不要求知道任何关于对象如何实现以及对象来自哪里的细节。

应用案例
  1. 应用创建对象的代码分布在多个不同的地方,而不是仅在一个函数中,这样无法跟踪对象。工厂方法集中的在一个地方创建对象,使得对象跟踪变得容易。
  2. 需要将对象创建和使用解耦。使用工厂方法,意味着改变对象时,只需修改创建对象的工厂函数就可以,无需修改使用对象的代码。
  3. 工厂方法可以在必要时创建新的对象,从而提高性能和内存使用率。
实现

实现一个工厂方法,可以根据输入文件是JSON类型还是XML返回不同的数据对象。

In [4]:
import xml.etree.ElementTree as etree
import 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
    def parsed_data(self):
        return self.data


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')
    print()
    pass
    
if __name__ == '__main__':
    main()
Cannot connect to data/person.sq3

抽象工厂

抽象工厂是一组工厂方法,其中每个工厂方法负责产生不同种类的对象。

应用案例

抽象工厂是工厂方法的一种泛化,具有工厂方法的所有优点。除此之外,在使用工厂方法时从用户视角通常是看不到的,那就是抽象工厂能够通过改变激活的工厂方法动态的(运行时)改变应用行为。
经典案例是:用户可以在使用应用时改版应用观感,而不需要终止应用然后重新启动。

实现

创建一个游戏,我们希望应用中至少包含两个游戏,一个面向孩子,一个面向成人。在运行时,基于用户输入,决定该创建哪个游戏并运行。
游戏创建部分由一个工厂函数来维护。

  • Frog World孩子的游戏
  • Wizard World成人的游戏
In [6]:
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'

# FrogWorld是抽象工厂,职责是创建主人公和障碍物。
# 区分创建爱你方法并使其名字通用,这让我们可以动态改变当前激活的工厂。
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):
    try:
        age = input('Welcome {}. How old are you? '.format(name))
        age = int(age)
    except ValueError as err:
        print("Age {} is invalid, please try \
        again…".format(age))
        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()
Hello. What's your name? niuhe
Welcome niuhe. How old are you? 23


	------ Wizard World ———
niuhe the Wizard battles against an evil ork and kills it!

代码很清晰,是能够自解释的。
GameEnvironment是游戏的主入口,它接受factory作为输入,用其创建游戏的世界。
方法play()会启动hero和obstacle之间的交互。

小结

工厂模式的应用场景:

  • 想追踪创建的对象
  • 对象创建和对象使用解耦
  • 优化应用的性能和资源占用

工厂模式两种类型:

  • 工厂方法
  • 抽象工厂

The END

Comments