天天看点

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

作者:风趣生命的起源

意图

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

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

问题

想象一下,您正在创建一个物流管理应用程序。您的应用程序的第一个版本只能处理卡车运输,因此大部分代码位于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())           

继续阅读