百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

经典创建型设计模式:工厂方法模式

liebian365 2024-10-21 08:46 6 浏览 0 评论

意图

工厂方法是一种创建型设计模式,它为超类提供了一个创建对象的接口,但允许子类改变将被创建的对象的类型。

问题

想象一下,您正在创建一个物流管理应用程序。您的应用程序的第一个版本只能处理卡车运输,因此大部分代码位于Truck类中。

过了一段时间,您的应用程序变得非常受欢迎。每天您都会收到海运公司的数十个请求,要求将海上物流纳入应用程序中。

这是个好消息,不是吗?但是代码怎么样呢?目前,大部分代码都与Truck类紧密耦合在一起。如果要将船只加入应用程序中,就需要对整个代码库进行修改。而且,如果以后决定在应用程序中添加另一种类型的运输方式,可能需要再次进行所有这些更改。

结果就是,您将得到一段相当糟糕的代码,充斥着根据运输对象的类来切换应用程序行为的条件语句。

解决方法

工厂方法模式建议您将直接的对象构建调用(使用new运算符)替换为对特殊工厂方法的调用。不用担心:对象仍然是通过new运算符创建的,只是该运算符是在工厂方法内部调用的。工厂方法返回的对象通常被称为产品。

乍一看,这个改变可能看起来毫无意义:我们只是将构造函数调用从程序的一部分移到另一部分。然而,请考虑以下情况:现在您可以在子类中重写工厂方法,并通过该方法改变所创建产品的类。

然而,有一个小小的限制:只有当这些产品具有共同的基类或接口时,子类才能返回不同类型的产品。此外,基类中的工厂方法应将其返回类型声明为该接口。

例如,Truck和Ship类都应该实现Transport接口,该接口声明了一个名为deliver的方法。每个类都以不同的方式实现了这个方法:卡车通过陆路交付货物,船只通过海上交付货物。RoadLogistics类中的工厂方法返回卡车对象,而SeaLogistics类中的工厂方法返回船只对象。

使用工厂方法的代码(通常称为客户端代码)不会看到各个子类返回的实际产品之间的区别。客户端将所有产品都视为抽象的Transport。客户端知道所有运输对象应该具有deliver方法,但它并不关心它是如何工作的。

结构


1、产品(Product)声明了一个接口,该接口对于所有可以由创建者及其子类生产的对象是通用的。

2、具体产品(Concrete Products)是产品接口的不同实现。

3、创建者(Creator)类声明了一个工厂方法,该方法返回新的产品对象。重要的是,该方法的返回类型与产品接口相匹配。

您可以将工厂方法声明为抽象的,以强制所有子类实现自己的方法版本。作为替代方案,基础工厂方法可以返回某种默认的产品类型。

需要注意的是,尽管它的名字是这样的,但产品的创建并不是创建者的主要责任。通常,创建者类已经具有与产品相关的一些核心业务逻辑。工厂方法有助于将这种逻辑与具体的产品类解耦。这里有一个类比:一个大型软件开发公司可以有一个面向程序员的培训部门。然而,公司作为一个整体的主要功能仍然是编写代码,而不是生产程序员。

4、具体创建者(Concrete Creators)覆盖基础工厂方法,以返回不同类型的产品。

需要注意的是,工厂方法并不一定需要始终创建新的实例。它也可以从缓存、对象池或其他来源返回现有的对象。

伪代码

这个例子说明了如何使用工厂方法在不将客户端代码与具体的UI类耦合的情况下创建跨平台的UI元素。

基础的Dialog类使用不同的UI元素来渲染其窗口。在各种操作系统下,这些元素可能看起来有些不同,但它们的行为应该保持一致。在Windows中的按钮在Linux中仍然是一个按钮。

当工厂方法发挥作用时,你无需为每个操作系统重新编写Dialog类的逻辑。如果我们在基础的Dialog类中声明一个生产按钮的工厂方法,然后创建一个子类,该子类从工厂方法中返回Windows风格的按钮。这个子类继承了大部分来自基础类的代码,但由于工厂方法的存在,它可以在屏幕上呈现出Windows风格的按钮。

为了使这个模式工作,基础的Dialog类必须使用抽象按钮:一个所有具体按钮都遵循的基类或接口。这样,Dialog内部的代码可以与任何类型的按钮一起正常工作。

当然,你也可以将这种方法应用到其他UI元素上。然而,每次向Dialog添加新的工厂方法,你就越接近抽象工厂模式。别担心,我们稍后会讨论这个模式。

// The creator class declares the factory method that must
// return an object of a product class. The creator's subclasses
// usually provide the implementation of this method.
class Dialog is
    // The creator may also provide some default implementation
    // of the factory method.
    abstract method createButton():Button

    // Note that, despite its name, the creator's primary
    // responsibility isn't creating products. It usually
    // contains some core business logic that relies on product
    // objects returned by the factory method. Subclasses can
    // indirectly change that business logic by overriding the
    // factory method and returning a different type of product
    // from it.
    method render() is
        // Call the factory method to create a product object.
        Button okButton = createButton()
        // Now use the product.
        okButton.onClick(closeDialog)
        okButton.render()


// Concrete creators override the factory method to change the
// resulting product's type.
class WindowsDialog extends Dialog is
    method createButton():Button is
        return new WindowsButton()

class WebDialog extends Dialog is
    method createButton():Button is
        return new HTMLButton()


// The product interface declares the operations that all
// concrete products must implement.
interface Button is
    method render()
    method onClick(f)

// Concrete products provide various implementations of the
// product interface.
class WindowsButton implements Button is
    method render(a, b) is
        // Render a button in Windows style.
    method onClick(f) is
        // Bind a native OS click event.

class HTMLButton implements Button is
    method render(a, b) is
        // Return an HTML representation of a button.
    method onClick(f) is
        // Bind a web browser click event.


class Application is
    field dialog: Dialog

    // The application picks a creator's type depending on the
    // current configuration or environment settings.
    method initialize() is
        config = readApplicationConfigFile()

        if (config.OS == "Windows") then
            dialog = new WindowsDialog()
        else if (config.OS == "Web") then
            dialog = new WebDialog()
        else
            throw new Exception("Error! Unknown operating system.")

    // The client code works with an instance of a concrete
    // creator, albeit through its base interface. As long as
    // the client keeps working with the creator via the base
    // interface, you can pass it any creator's subclass.
    method main() is
        this.initialize()
        dialog.render()

适用性

1、当你事先不知道代码应该使用的对象的确切类型和依赖关系时,可以使用工厂方法。

工厂方法将产品构造代码与实际使用产品的代码分离开来。因此,可以更容易地独立扩展产品构造代码,而不影响其他代码。

例如,要向应用程序添加新的产品类型,只需创建一个新的创建者子类并在其中重写工厂方法即可。

2、当你希望为库或框架的用户提供一种扩展其内部组件的方式时,可以使用工厂方法。

继承可能是扩展库或框架默认行为最简单的方式。但是,框架如何识别应该使用你的子类而不是标准组件呢?

解决方案是将整个框架中构造组件的代码减少为一个单独的工厂方法,并允许任何人在扩展组件的同时重写此方法。

让我们看看这将如何运作。假设你使用一个开源的UI框架编写应用程序。你的应用程序应该有圆形按钮,但框架只提供方形按钮。你通过将标准Button类扩展为一个名为RoundButton的子类来解决这个问题。但现在你需要告诉主要的UIFramework类使用新的按钮子类而不是默认按钮。

为了实现这一点,你创建一个从基本框架类继承的UIWithRoundButtons子类,并重写它的createButton方法。在基类中,这个方法返回Button对象,而在你的子类中,你让它返回RoundButton对象。现在使用UIWithRoundButtons类而不是UIFramework类。就是这样了!

3、当你希望通过重用现有对象而不是每次重新构建对象来节省系统资源时,可以使用工厂方法。

当处理大型、资源密集型对象(如数据库连接、文件系统和网络资源)时,通常会遇到这种需求。

让我们思考一下如何重用现有对象:

首先,你需要创建一些存储空间来跟踪所有已创建的对象。 当有人请求一个对象时,程序应该在这个池中寻找一个空闲的对象。 然后将其返回给客户端代码。 如果没有空闲的对象,程序应该创建一个新对象(并将其添加到池中)。 这是很多代码!而且必须将它们放在一个地方,以免在程序中出现重复的代码。

可能最明显、最方便的地方是将这些代码放在我们尝试重用对象的类的构造函数中。然而,根据定义,构造函数必须始终返回新对象。它不能返回现有实例。

因此,你需要一个常规方法,既能创建新对象,又能重用现有对象。这听起来非常像一个工厂方法。

如何实现

让所有产品遵循相同的接口。这个接口应该声明在每个产品中都有意义的方法。

在创建者类中添加一个空的工厂方法。该方法的返回类型应与通用产品接口相匹配。

在创建者的代码中找到所有对产品构造函数的引用。逐个用对工厂方法的调用替换它们,并将产品创建代码提取到工厂方法中。

可能需要在工厂方法中添加一个临时参数来控制返回产品的类型。

此时,工厂方法的代码可能看起来非常丑陋。它可能有一个大的 switch 语句来选择实例化哪个产品类。但不要担心,我们很快就会修复它。

现在,为工厂方法中列出的每种产品类型创建一组创建者子类。在子类中重写工厂方法,并从基类方法中提取适当的构造代码。

如果产品类型太多,为所有产品类型创建子类没有意义,你可以在子类中重用基类中的控制参数。

例如,假设你有以下类层次结构:基类 Mail,具有几个子类:AirMail 和 GroundMail;Transport 类有 Plane、Truck 和 Train。AirMail 类只使用 Plane 对象,GroundMail 可能与 Truck 和 Train 对象一起工作。你可以创建一个新的子类(比如 TrainMail)来处理这两种情况,但还有另一种选择。客户端代码可以向 GroundMail 类的工厂方法传递参数来控制它想要接收的产品。

如果在所有提取操作之后,基本工厂方法变为空的,你可以将其声明为抽象方法。如果还有剩余的代码,可以将其作为方法的默认行为。

Python示例

from __future__ import annotations
from abc import ABC, abstractmethod


class Creator(ABC):
    """
    The Creator class declares the factory method that is supposed to return an
    object of a Product class. The Creator's subclasses usually provide the
    implementation of this method.
    """

    @abstractmethod
    def factory_method(self):
        """
        Note that the Creator may also provide some default implementation of
        the factory method.
        """
        pass

    def some_operation(self) -> str:
        """
        Also note that, despite its name, the Creator's primary responsibility
        is not creating products. Usually, it contains some core business logic
        that relies on Product objects, returned by the factory method.
        Subclasses can indirectly change that business logic by overriding the
        factory method and returning a different type of product from it.
        """

        # Call the factory method to create a Product object.
        product = self.factory_method()

        # Now, use the product.
        result = f"Creator: The same creator's code has just worked with {product.operation()}"

        return result


"""
Concrete Creators override the factory method in order to change the resulting
product's type.
"""


class ConcreteCreator1(Creator):
    """
    Note that the signature of the method still uses the abstract product type,
    even though the concrete product is actually returned from the method. This
    way the Creator can stay independent of concrete product classes.
    """

    def factory_method(self) -> Product:
        return ConcreteProduct1()


class ConcreteCreator2(Creator):
    def factory_method(self) -> Product:
        return ConcreteProduct2()


class Product(ABC):
    """
    The Product interface declares the operations that all concrete products
    must implement.
    """

    @abstractmethod
    def operation(self) -> str:
        pass


"""
Concrete Products provide various implementations of the Product interface.
"""


class ConcreteProduct1(Product):
    def operation(self) -> str:
        return "{Result of the ConcreteProduct1}"


class ConcreteProduct2(Product):
    def operation(self) -> str:
        return "{Result of the ConcreteProduct2}"


def client_code(creator: Creator) -> None:
    """
    The client code works with an instance of a concrete creator, albeit through
    its base interface. As long as the client keeps working with the creator via
    the base interface, you can pass it any creator's subclass.
    """

    print(f"Client: I'm not aware of the creator's class, but it still works.\n"
          f"{creator.some_operation()}", end="")


if __name__ == "__main__":
    print("App: Launched with the ConcreteCreator1.")
    client_code(ConcreteCreator1())
    print("\n")

    print("App: Launched with the ConcreteCreator2.")
    client_code(ConcreteCreator2())

相关推荐

快递查询教程,批量查询物流,一键管理快递

作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...

一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递

对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...

快递查询单号查询,怎么查物流到哪了

输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...

3分钟查询物流,教你一键批量查询全部物流信息

很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...

快递单号查询,一次性查询全部物流信息

现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...

快递查询工具,批量查询多个快递快递单号的物流状态、签收时间

最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...

快递查询软件,自动识别查询快递单号查询方法

当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...

教你怎样查询快递查询单号并保存物流信息

商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...

简单几步骤查询所有快递物流信息

在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...

物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号

最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...

连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息

快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...

快递查询教程,快递单号查询,筛选更新量为1的单号

最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...

掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析

在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...

从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息

在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...

物流单号查询,在哪里查询快递

如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...

取消回复欢迎 发表评论: