ITPub博客

首页 > 数据库 > 数据库开发技术 > Python设计模式(二):工厂模式(Factory Pattern)

Python设计模式(二):工厂模式(Factory Pattern)

原创 数据库开发技术 作者:jackzhang 时间:2024-07-21 06:30:00 0 删除 编辑

哈喽,大家好啊,之前笔者写了一篇关于单例模式(Singleton Pattern)的文章(你知道单例模式吗?Python如何实现?),有读者认为写得还可以,希望笔者再写写其他设计模式的文章。首先感谢读者朋友的认可,今天我将向大家分享另一种设计模式——工厂模式(Factory Pattern)的用法和实现。

工厂模式可以说是最常用的的设计模式。在面向对象编程(OOP)中,工厂这一术语指的是负责创建其他类型对象的一个类。与生活中的原理很相似,你可以理解成现实中工厂向客户提供不同的模板,由客户决定自己需要实现的模板,然后提交给工厂生产后将产品返回给客户。

本文,我们将会介绍以下主题:

  • 理解简单工厂模式及Python案例实现
  • 工厂方法模式、Python案例实现及优缺点
  • 抽象工厂模式、Python案例实现及优缺点
  • 工厂方法模式 VS 抽象工厂模式

前言:理解工厂模式

通常,充当工厂的类具有一个对象和与之关联的方法。客户端会用特定参数调用这个方法,目标类型的对象由工厂依次创建并返回给客户端。

可能有人会问,既然客户端可以直接创建对象,为什么我们还需要工厂呢?答案就是,工厂可以提供以下优点:

  • 松耦合。工厂可以让对象的创建和类的实现相互独立。
  • 客户端实现简单。客户端不需要知道创建对象的类,只需要知道接口、方法和需要传递的参数,以便创建所需类型的对象。这样可以避免暴露对象创建逻辑,同时也简化了客户端的实现。
  • 可维护性强。向工厂添加另一个类以创建另一种类型的对象,可以在不改变客户端代码的情况下轻松完成。客户端只需要传递另一个参数即可。
  • 资源节约。工厂还可以重用现有对象。然而,如果客户端直接创建对象,总是会产生一个新的对象,可能会造成资源的浪费和其他隐性问题。

我们来考虑一个服装制造(T 恤和牛仔裤)公司的场景。假设公司的一台机器现在正在生产 T 恤,然而,公司的高层根据现在的市场需求,迫切需要生产牛仔裤。这个场景就会用到工厂模式。在这个场景下,机器会称为接口,而公司高层则是客户端。高层只关心要制造的对象(或服装),并且知道可以创建对象的接口——机器。

工厂模式有三种变体:

  • 简单工厂模式(Simple Factory Pattern):这允许接口在不暴露对象创建逻辑的情况下创建对象。
  • 工厂方法模式(Factory Method Pattern):这允许接口创建对象,但将决定对象创建类别的决策交给子类。
  • 抽象工厂模式(Abstract Factory Pattern):抽象工厂是一个接口,用于创建相关对象而不指定/暴露它们的类。该模式提供另一个工厂的对象,该对象在内部创建其他对象。

1. 简单工厂模式(Simple Factory Pattern)

严格意义上来说,简单工厂本身并不是一个独有的模式。它更多是一个开发者在了解工厂方法和抽象工厂方法之前需要知道的一个概念。工厂用于创建不同类型的对象,而不是实例化对象。

让我们通过以下图表来理解这一点。在这里,客户端类使用工厂类,工厂类有 create_type() 方法。当客户端调用带有类型参数的 create_type() 方法时,根据传递的参数,工厂返回 Product1 或 Product2:

现在让我们用一个Python实例来帮助我们理解简单工厂模式。在下面的代码片段中,我创建了一个叫 Clothing 的抽象产品。Clothing 是一个抽象基类,它有一个 get_description() 方法。接下里,我根据 Clothing 接口创建两个产品(Tshirt 和 Jeans),并且用这些服装特有的特征描述实现了 get_description() 方法。ClothingFactory 是一个具有 create_clothing() 方法的工厂。基于客户端穿点的参数类型,在运行时工厂会创建合适的服装实例,并正确打印出服装描述。

from abc import ABC, abstractmethod

class Clothing(ABC):
    @abstractmethod
    def get_description(self):
        """ Returns the description of the clothing """
        pass


class Tshirt(Clothing):
    def get_description(self):
        return "This is a T-shirt!"


class Jeans(Clothing):
    def get_description(self):
        return "This is a pair of jeans!"


# Define a simple factor
class ClothingFactory(object):
    @staticmethod
    def create_clothing(object_type):
        if object_type == "T-shirt":
            return Tshirt()
        elif object_type == "Jeans":
            return Jeans()
        else:
            raise ValueError("Unknown clothing type!")


# Define client code
def client_code():
    cf = ClothingFactory()
    clothing_type = input("Enter a clothing type (T-shirt/Jeans):  ")
    try:
        clothing = cf.create_clothing(clothing_type)
        print(clothing.get_description())
    except ValueError as e:
        print(e)


if __name__ == '__main__':
    client_code()

2. 工厂方法模式(Factory Method Pattern)

工厂方法模式是一种创建型设计模式,它定义了一个用于创建对象的接口,但让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。

工厂方法的创建通过继承实现,而不是实例化,此外,工厂方法使得设计更加可定制化,它可以返回相同的实例或子类,而不是某个类型的对象。

在前面的UML图中,我们有一个抽象类 Creator,它包含 factoryMethod() 方法。factoryMethod() 方法负责创建某种类型的对象。ConcreteCreator  类拥有 factoryMethod(),它实现了 Creator 抽象类,这个方法可以在运行时改变创建的对象。

ConcreteCreator 创建 ConcreteProduct,并确保它创建的对象实现了 Product 类,并实现 Product 接口中的所有方法。

简而言之,工厂方法模式提供了一种创建对象的接口,由子类决定实例化哪个具体类。这样,工厂方法将实例化操作推迟到子类进行。这个模式包含以下几个角色:

  • 产品(Product):定义工厂方法所创建的对象的接口。
  • 具体产品(ConcreteProduct):实现产品接口的具体类。
  • 创建者(Creator):声明返回产品对象的工厂方法。
  • 具体创建者(ConcreteCreator):实现工厂方法,返回具体产品的实例。

2.1 实现工厂方法模式

工厂方法模式适用于以下场景:

  • 创建对象的代码不能预见其具体类:当一个类无法预见它必须创建的对象类型时。
  • 类希望将创建对象的职责委托给子类:当类希望将创建对象的职责委托给子类,而不是具体的实现类。
  • 避免创建对象时与具体类的紧耦合:需要将对象创建的具体实现与使用分离,从而避免紧耦合。

真实场景应用案例——日志记录器

假设需要一个日志记录器,可以实现将日志记录到不同的地方(如文件、数据库或控制台)。使用工厂方法模式,可以为每种日志记录方式创建一个具体的日志记录器类,通过工厂方法决定使用哪种记录方式。

from abc import ABC, abstractmethod

# Product interface (Product role)
class Logger(ABC):
    @abstractmethod
    def log(self, message: str) -> None:
        pass

# Concrete Product (ConcreteProduct role)
class FileLogger(Logger):
    def log(self, message: str) -> None:
        print(f"Logging to file: {message}")

class DatabaseLogger(Logger):
    def log(self, message: str) -> None:
        print(f"Logging to database: {message}")

class ConsoleLogger(Logger):
    def log(self, message: str) -> None:
        print(f"Logging to console: {message}")

# Creator (Creator role)
class LoggerFactory(ABC):
    @abstractmethod
    def create_logger(self) -> Logger:
        pass

# Concrete Creator (ConcreteCreator role)
class FileLoggerFactory(LoggerFactory):
    def create_logger(self) -> Logger:
        return FileLogger()

class DatabaseLoggerFactory(LoggerFactory):
    def create_logger(self) -> Logger:
        return DatabaseLogger()

class ConsoleLoggerFactory(LoggerFactory):
    def create_logger(self) -> Logger:
        return ConsoleLogger()

# Client code
def client_code(factory: LoggerFactory) -> None:
    logger = factory.create_logger()
    logger.log("This is a log message.")

if __name__ == '__main__':
    client_code(FileLoggerFactory())
    client_code(DatabaseLoggerFactory())
    client_code(ConsoleLoggerFactory())

练习题:在一个应用程序中,需要处理不同类型的文档(如Word文档、PDF文档、Excel文档)。请通过工厂方法模式,为每种文档类型创建一个具体的文档类,并通过工厂方法决定创建哪种文档。感兴趣的朋友可以参考日志记录器自己尝试实现,毕竟读得再多都不如亲自动手实践嘛。

2.2 工厂方法模式的优缺点

优点:

  • 遵循开放/封闭原则:增加新的产品时无需修改已有代码,符合开放/封闭原则。
  • 提高可扩展性:通过引入新的工厂子类,可以在不修改客户端代码的情况下添加新产品。
  • 降低耦合:客户端代码与具体产品解耦,仅依赖于抽象产品接口。

缺点:

  • 类数量增加:每添加一个新产品都需要增加一个新的具体工厂类,导致类的数量增加。
  • 复杂性增加:对于简单对象的创建,这种模式可能显得过于复杂。

总之,工厂方法模式通过引入工厂方法接口和具体工厂子类,将对象创建的责任分离,使代码更灵活、更易扩展。

3. 抽象工厂模式(Abstract Factory Pattern)

抽象工厂模式(Abstract Factory Pattern)是一种创建型设计模式,它提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。这个模式有助于系统在不修改代码的情况下切换产品系列。

主要角色

  • 抽象工厂(Abstract Factory):声明创建抽象产品对象的操作接口。
  • 具体工厂(Concrete Factory):实现抽象工厂的接口,负责创建具体的产品实例。
  • 抽象产品(Abstract Product):为每种产品类型声明一个接口。
  • 具体产品(Concrete Product):定义一个将被具体工厂创建的产品对象,必须实现抽象产品接口。
  • 客户端(Client):使用抽象工厂和抽象产品的接口来创建一个具体的产品对象。

如上面的UML图所示,ConcreteFactory1 和 ConcreteFactory2 由 AbstractFactory 接口创建。这个接口拥有创建多个产品的方法。

ConcreteFactory1 和 ConcreteFactory2 实现了抽象工厂并且创建了 ConcreteProductA1、ConcreteProductA2、ConcreteProductB1 和 ConcreteProductB2 的实例。而 ConcreteProductA1 和 ConcreteProductA2 由 AbstractProductA 接口创建;同样地,ConcreteProductB1 和 ConcreteProductB2 则由 AbstractProductB 接口所创建。

抽象工厂模式确保客户端与对象创建隔离,同时允许客户端使用创建的对象。客户端通过接口访问对象,仅能使用同一系列产品的对象。对于需要平台独立性的应用,抽象工厂模式可以抽象出操作系统、文件系统调用等依赖,使得客户端无需直接创建平台对象,从而简化了开发过程。

3.1 实现抽象工厂模式

抽象工厂模式适用于需要创建一系列相关或相互依赖对象的场景,而无需指定具体类。以下是一些常见的应用场景:

  • 跨平台应用开发:在开发需要跨多个操作系统或设备平台运行的应用程序时,抽象工厂模式可以帮助创建不同平台上的用户界面(UI)组件。每个平台有不同的外观和行为,但从客户端代码来看,它们可以通过统一的接口进行访问和操作。

  • GUI 工具包:不同的 GUI 工具包可能需要创建一系列互相关联的控件(如按钮、复选框、文本框等),抽象工厂模式可以确保这些控件在外观和行为上的一致性。例如,Swing 和 AWT 是 Java 中的两种 GUI 工具包,它们的控件有不同的实现。

  • 数据库访问层:当应用程序需要支持多种数据库时,可以使用抽象工厂模式创建不同数据库的连接、查询和事务对象。例如,可以为 MySQL、PostgreSQL、Oracle 等数据库创建不同的工厂,每个工厂提供相应的数据库连接对象。

  • 产品族的生产:当需要创建一系列相关产品对象时,抽象工厂模式可以确保这些产品的一致性。例如,创建不同品牌的家具(如椅子、桌子、沙发等),不同品牌的家具风格可能不同,但它们的接口是统一的。

  • 依赖对象的创建:当一个对象的创建依赖于另一个对象时,抽象工厂模式可以管理这些依赖关系。例如,在一个游戏中,创建一个敌人对象可能需要创建不同的武器、盔甲和技能对象,这些对象可以通过抽象工厂模式进行管理。

  • 配置文件或环境切换:在应用程序中,根据不同的配置文件或运行环境创建不同的对象。例如,在开发环境和生产环境中使用不同的日志记录对象、缓存对象等,可以使用抽象工厂模式来实现环境的无缝切换。

真实场景应用案例——跨平台文本编辑器

假设我们需要开发一个跨平台的文本编辑器,它需要在 Windows 和 MacOS 上运行,并且在不同平台上有不同的 UI 风格。我们可以使用抽象工厂模式来实现这一需求。

from abc import ABC, abstractmethod

# 1. Abstract factory interface
class GUIFactory(ABC):
    @abstractmethod
    def create_button(self):
        pass

    @abstractmethod
    def create_textbox(self):
        pass

# 2. Abstract product interface
class Button(ABC):
    @abstractmethod
    def render(self) -> str:
        pass

class Textbox(ABC):
    @abstractmethod
    def render(self) -> str:
        pass

# 3. Concrete factory classes
class WindowsFactory(GUIFactory):
    def create_button(self) -> Button:
        return WindowsButton()

    def create_textbox(self) -> Textbox:
        return WindowsTextbox()

class MacOSFactory(GUIFactory):
    def create_button(self) -> Button:
        return MacOSButton()

    def create_textbox(self) -> Textbox:
        return MacOSTextbox()

# 4. Concrete product classes
class WindowsButton(Button):
    def render(self) -> str:
        return "Render a button in windows style"

class WindowsTextbox(Textbox):
    def render(self) -> str:
        return "Render a textbox in windows style"

class MacOSButton(Button):
    def render(self) -> str:
        return "Render a button in MacOS style"

class MacOSTextbox(Textbox):
    def render(self) -> str:
        return "Render a textbox in MacOS style"

# 5. Client code
def create_ui(factory: GUIFactory) -> None:
    button = factory.create_button()
    textbox = factory.create_textbox()

    print(button.render())
    print(textbox.render())

if __name__ == '__main__':
    # 在 Windows 平台上创建 UI
    print("Creating UI for Windows platform:")
    create_ui(WindowsFactory())

    # 在 MacOS 平台上创建 UI
    print("\nCreating UI for MacOS platform:")
    create_ui(MacOSFactory())

练习:感兴趣的朋友可以自己实现应用场景中的“数据库访问层”或是其他场景。

3.2 抽象工厂模式的优缺点

优点

  • 分离接口和实现:将具体类的创建封装起来,客户端仅使用接口。
  • 产品族一致:确保同一工厂生产的产品是一致的,可以避免产品间不兼容的问题。

缺点

  • 增加复杂性:如果只需要创建少量的产品,使用抽象工厂模式会使系统变得更加复杂。
  • 扩展困难:增加新的产品族需要修改抽象工厂接口及其所有的具体工厂类。

总之,抽象工厂模式特别适用于产品族的创建情景,例如 GUI 工具包(不同操作系统的不同控件)、数据库访问层(不同数据库的不同连接器)等。

4. 工厂方法模式 VS 抽象工厂模式

工厂方法和抽象工厂模式都是创建型设计模式,它们在创建对象的方式和目的上有一些相似之处,但也有明显的不同。以下是它们之间的主要异同点,用表格形式呈现:

比较维度 工厂方法模式 抽象工厂模式
意图 定义一个创建对象的接口,让子类决定实例化哪个类。 提供一个接口,用于创建相关或依赖对象的家族,而不需要指定具体类。
类的层次结构 使用继承来决定哪一个具体类来实例化。 使用组合来创建对象家族。
创建对象的数量 一个工厂方法创建一种产品。 一个工厂创建多个相关的产品。
抽象层次 仅有一个抽象产品和一个抽象创建方法。 有多个抽象产品和一个抽象工厂接口,每个工厂可以创建多个产品。
适用范围 当一个类不能预见其所需要的具体对象时。 当系统需要一系列相关或依赖对象,并且需要通过配置文件或运行时选择具体工厂时。
扩展性 添加新的产品子类时,需要改变具体的工厂类。 添加新的产品族时,需要扩展整个工厂接口及其具体实现。
复杂性 相对简单,只需要一个工厂方法。 相对复杂,需要定义多个接口和类。
示例场景 日志记录器,根据配置创建不同类型的日志记录器。 跨平台UI库,根据平台创建不同的UI组件(如按钮、文本框等)。
客户使用方法 客户端调用工厂方法来创建具体产品实例。 客户端调用抽象工厂的方法来创建一组相关的产品实例。

好啦,关于工厂模式的知识今天就分享到这啦!感谢你的阅读!


推荐阅读👇👇👇

每天都在用的20个Python技巧,让你从此告别平庸!

编写高效Python函数的5个技巧,真心建议你牢记,并当作此后编码的准绳!

你知道单例模式吗?Python如何实现?

机器学习基础:今天的主角—逻辑回归(Logistics Regression)!献给正在入门机器学习的你!

机器学习基础:一文搞懂简单线性回归的原理,新手也能看懂!

- END -

🌟 如果你喜欢这篇文章并且愿意支持我的话,你可以:🌟

  • 👍👍👍 点赞文章
  • ⤴️⤴️⤴️ 转发文章
  • ⚛️⚛️⚛️ 点击在看
  • 🧑‍🤝‍🧑🧑‍🤝‍🧑🧑‍🤝‍🧑 让文章被推荐给更多人,找到更多志同道合的伙伴
  • 👏👏👏 欢迎给我留言

关注我,第一时间接收更多关于Python、数据分析和机器学习的深入分析和最新趋势。我们一起探索数据的力量,解锁AI的潜能。你的每一次互动都是对我的极大鼓励,也是我们共同成长的动力!

👇👇👇 关注并开启星标提醒,让知识与技能的火花不断在你的职业道路上燃烧吧!

原文作者:数据派探险家;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2017-12-19

  • 博文量
    119
  • 访问量
    55941