Python: 开始使用工厂模式设计

1. 写在前面

我们都知道,设计模式是一组最佳实践,可用于解决软件开发中反复出现的问题。在本文中,我们将介绍另一种设计模式——工厂模式。

公众号: 滑翔的纸飞机

2. 工厂模式

2.1 介绍

工厂模式是由一个工厂对象根据不同参数创建不同的实例。具体传什么参数,创建什么实例的逻辑是在工厂对象中完成的。简单点就是:在不指定确切类的情况下创建对象。

优点:

只需要传入一个正确的参数,就可以获取你所需要的对象而无需知道其创建细节。

缺点:

工厂一旦需要生产新产品就需要修改工厂类的方法逻辑,违背了开放—封闭原则;
如果产品实例种类很多,也导致了工厂内部逻辑复杂,不易维护。

为避免工厂类因业务复杂代码庞大,可以拆分成一个个的工厂类,这样代码就不会都耦合在同一个类里了,进一步降低程序的耦合性。

2.2 基本实现

通过一个简单例子,看看工厂模式如何在Python中实现;

示例场景:根据用户输入创建不同类型的动物;

"""
@Time:2023/9/20 00:58
@Author:'jpzhang.ht@gmail.com'
@Describe:
"""


class Animal:
    def say(self):
        pass


class Dog(Animal):
    def say(self):
        return "Woof!"


class Cat(Animal):
    def say(self):
        return "Meow!"

# 工厂
class AnimalFactory:
    def create_animal(self, animal_type):
        if animal_type == "dog":
            return Dog()

        elif animal_type == "cat":
            return Cat()

        else:
            raise ValueError("Invalid animal type")

# 入口
if __name__ == '__main__':
    factory = AnimalFactory()
    animal1 = factory.create_animal("dog")
    animal2 = factory.create_animal("cat")


  • Animal 定义为父类,包含一个方法,用来表示动物叫声。同时定义了2个子类(Dog、Cat)继承该父类,覆盖此方法,以返回子类所定义动物的叫声;
  • AnimalFactory 定义为工厂类,同时包含一个方法create_animal,该方法通过传入参数animal_type返回相应动物实例对象;
  • __main__ 入口,创建一个工厂类实例,并通过类方法创建两种不同类型的动物;

如上示例就是一个简单的工厂模式。

2.3 如何工作?

在工厂模式中,有一个 Factory 类,它定义了用于创建对象的方法。允许客户端代码在不知道将要创建对象的确切类型情况下创建对象。

基本流程如下:

  • 客户端在 Factory 接口上调用工厂方法。
  • 创建一个具体类型的实例对象并将其返回到客户端。
  • 客户端使用“具体类型”的实例对象。

要素:

(1)抽象产品类(Product):抽象方法&公共接口,产品子类具体实现及继承;
(2)具体产品类(ConcreteProduct,继承抽象产品类):定义具体产品实现;
(3)抽象工厂类(Factory):提供抽象方法;
(4)具体工厂类(ConcreteFactory,继承抽象工厂类):定义创建具体产品实例的方法;

2.4 使用案例

(1)示例一:在复杂系统中创建对象任务时,工厂模式可能是一个有用的工具

对象创建可能是一项复杂的任务,尤其是在大型系统中,其中可能需要根据不同的配置或要求创建许多不同类型的对象。

手动创建对象可能非常耗时、容易出错,并且可能导致代码重复。这就是抽象工厂等设计模式有用的地方。

抽象工厂模式提供了一个接口,用于创建相关对象或依赖对象,而无需指定其具体类。

例如:假设你正在构建一个咖啡店应用程序,客户可以在其中订购不同类型的咖啡。每种咖啡都有一个名称、价格和配料。你可以将每种类型的咖啡表示为 Python 类,并定义一个基于其成分计算咖啡成本的方法。

"""
@Time:2023/9/21 00:52
@Author:'jpzhang.ht@gmail.com'
@Describe:
"""


class Coffee:
    def __init__(self, name, price, ingredients):
        self.name = name  # 名称
        self.price = price  # 价格
        self.ingredients = ingredients  # 成分

    def cost(self):
        # 计算费用
        return sum([ingredient.cost() for ingredient in self.ingredients])


class Espresso(Coffee):
    # 浓缩咖啡
    def __init__(self):
        super().__init__('Espresso', 2.0, [CoffeeBean(), Water()])


class Latte(Coffee):
    # 拿铁咖啡
    def __init__(self):
        super().__init__('Latte', 3.0, [CoffeeBean(), Milk(), Water()])

Coffee是定义咖啡基本属性的抽象类。EspressoLatte是具体的子类,定义了每种咖啡的特定成分和价格。

现在,假设你要向应用程序添加一种新型咖啡,例如卡布奇诺咖啡。你需要创建该类的新子类,并定义卡布奇诺的特定成分和价格。

工厂模式实现:

class CoffeeFactory:
    def create_coffee(self, coffee_type):
        if coffee_type == 'espresso':
            return Espresso()
        elif coffee_type == 'latte':
            return Latte()
        else:
            raise ValueError(f"Invalid coffee type: {coffee_type}")

该类有一个方法(create_coffee),该方法接受一个参数并返回相应类型咖啡的新实例。现在,当你想在应用程序中创建新咖啡时,只需调用类的方法:

factory = CoffeeFactory()
espresso = factory.create_coffee('espresso')
latte = factory.create_coffee('latte')

(2)工厂模式的另一个用例:涉及创建多个对象实例的问题

假设你正在 Python 中构建一个简单的银行应用程序,并且你已经定义了一个类来表示银行帐户。该类具有当前存储帐户余额的属性,以及从帐户中存入和提取资金的方法:

class BankAccount:
    # 银行账户
    def __init__(self, balance=0):
        # 余额
        self.balance = balance

    def deposit(self, amount):
        # 存
        self.balance += amount

    def withdraw(self, amount):
        # 取
        if amount > self.balance:
            raise ValueError("Insufficient balance")
        self.balance -= amount

现在,假设你创建类的两个实例,每个实例用于不同客户:

customer1_account = BankAccount()
customer2_account = BankAccount()

如果两个客户都向他们的账户存入一些钱,你会期望他们的账户余额会相应地更新。例如:

customer1_account.deposit(100)
customer2_account.deposit(50)
print(customer1_account.balance)  # prints 100
print(customer2_account.balance)  # prints 50

但是,如果你不小心将一个帐户对象分配给另一个帐户对象,该怎么办?例如:

customer1_account = customer2_account
customer1_account.deposit(100)
print(customer2_account.balance)  # prints 100, not 50!

发生这种情况是因为两者现在都指向内存中的同一对象。因此,当你将钱存入时,你实际上是在更新共享对象的余额,这也会影响customer2_account.balance的余额。这可能会导致程序中出现意外行为和难以调试的错误。

若要避免此问题,可以使用像单例模式这样的设计模式来确保只创建类的一个实例,并在需要使用它的程序的之间共享。
这可以帮助你管理对象创建并防止与多个对象实例相关的问题。

下面是上述方案在工厂模式中的实现:

"""
@Time:2023/9/21 00:52
@Author:'jpzhang.ht@gmail.com'
@Describe:
"""

class BankAccount:
    def __init__(self, balance=0):
        # 余额
        self.balance = balance

    def deposit(self, amount):
        # 存
        self.balance += amount

    def withdraw(self, amount):
        # 取
        if amount > self.balance:
            raise ValueError("Insufficient balance")
        self.balance -= amount


class BankAccountFactory:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.accounts = {}
        return cls._instance

    def create_account(self, account_number, balance=0):
        if account_number not in self.accounts:
            account = BankAccount(balance)
            self.accounts[account_number] = account
        else:
            account = self.accounts[account_number]
        return account

在此实现中,我们使用工厂模式来创建和管理类的实例。该类被设计为单例,因此程序中只有一个实例。该方法将帐号和余额作为参数,并返回一个对象。如果字典中已存在帐号,则该方法返回现有对象;否则,它会创建一个新字典并将其添加到字典中。

下面是如何使用类创建和管理对象的示例:

if __name__ == '__main__':
    factory = BankAccountFactory()

    account1 = factory.create_account('123')
    account2 = factory.create_account('456')

    account1.deposit(100)
    account2.deposit(50)

    print(account1.balance)  # prints 100
    print(account2.balance)  # prints 50

    account3 = factory.create_account('123')  # returns existing account
    account3.deposit(50)
    print(account1.balance)  # prints 150, not 50!

输出:
100
50
150

如你所见,确保每个对象只有一个实例,这可以防止与多个对象实例相关的问题。

(3)数据处理是工厂模式可能有用的另一种方案

例如:考虑数据处理,包括:清理、转换和加载。你可以创建一个工厂类,为每种类型的数据生成相应的步骤。

工厂类提供一种基于输入数据类型生成处理步骤的方法

以下是实现:

"""
@Time:2023/9/21 01:15
@Author:'jpzhang.ht@gmail.com'
@Describe:
"""


class DataProcessor:
    # 数据处理器
    def process(self, data):
        pass


class CleanDataProcessor(DataProcessor):
    # 数据清理
    def process(self, data):
        # code to clean data
        return data


class TransformDataProcessor(DataProcessor):
    # 数据转换
    def process(self, data):
        # code to transform data
        return data


class LoadDataProcessor(DataProcessor):
    # 数据加载
    def process(self, data):
        # code to load data
        return data


class DataProcessorFactory:
    # 数据处理工厂
    @staticmethod
    def create_data_processor(processor_type):
        if processor_type == "clean":
            return CleanDataProcessor()
        elif processor_type == "transform":
            return TransformDataProcessor()
        elif processor_type == "load":
            return LoadDataProcessor()
        else:
            raise ValueError("Invalid processor type")


if __name__ == '__main__':
    data = [1, 2, 3, 4, 5]
    processor = DataProcessorFactory.create_data_processor("clean")
    data = processor.process(data)

    processor = DataProcessorFactory.create_data_processor("transform")
    data = processor.process(data)

    processor = DataProcessorFactory.create_data_processor("load")
    data = processor.process(data)

在此示例中,DataProcessor类充当不同类型的数据处理步骤(清理、转换和加载)的基类。DataProcessorFactory类是封装对象创建过程的工厂类,方法(create_data_processor)接受一个参数,并根据提供的类型返回对应实例。

3 最后

工厂是一种强大而灵活的设计模式,广泛用于软件开发。它提供了一种创建对象的方法,而无需指定将要创建的确切对象类,并将对象创建过程集中在一个位置。通过使用工厂方法,开发人员可以根据某些输入创建不同类型的对象,从而使代码更加灵活、可维护且不易出错。

感谢您花时间阅读文章
关注公众号不迷路:)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值