经典创建型设计模式:抽象工厂模式

经典创建型设计模式:抽象工厂模式

首页休闲益智家具制作模拟器更新时间:2024-05-06
意图

抽象工厂是一种创造性的设计模式,它可以让您生产一系列相关的对象,而无需指定它们的具体类别。

问题

想象一下,您正在创建一个家具店模拟器。您的代码包含代表以下内容的类:

  1. 一系列相关的产品,比如:椅子 沙发 咖啡桌。
  2. 该系列的几个变体。例如,产品椅子 沙发 咖啡桌有以下几个变体可供选择:现代风格、维多利亚风格、装饰艺术风格。

您需要一种创建单独家具对象的方法,以使它们与同一系列的其他对象相匹配。当顾客收到不匹配的家具时,他们会感到非常生气。

此外,您不希望在向程序添加新产品或产品系列时更改现有的代码。家具供应商经常更新他们的目录,您不希望每次更新时都更改核心代码。

解决方法

abstract Factory模式首先建议为产品族中的每个不同产品明确声明接口(例如,椅子、沙发或咖啡桌)。然后,您可以使所有产品的变体都遵循这些接口。例如,所有椅子的变体都可以实现椅子接口;所有咖啡桌的变体都可以实现咖啡桌接口,依此类推。

接下来的步骤是声明抽象工厂——一个具有产品族中所有产品的创建方法列表的接口(例如,createChair、createSofa和createCoffeeTable)。这些方法必须返回之前提取出来的接口所表示的抽象产品类型:Chair、Sofa、CoffeeTable等等。

现在,产品的不同变体怎么处理呢?对于产品族的每个变体,我们基于抽象工厂接口创建一个独立的工厂类。工厂是一个返回特定类型产品的类。例如,现代家具工厂(ModernFurnitureFactory)只能创建现代椅子(ModernChair)、现代沙发(ModernSofa)和现代咖啡桌(ModernCoffeeTable)对象。

客户端代码必须通过它们各自的抽象接口与工厂和产品一起工作。这样,你可以在不破坏实际客户端代码的情况下,更改传递给客户端代码的工厂类型和接收到的产品变体。

假设客户端需要一个生产椅子的工厂。客户端无需知道工厂的具体类,也不需要关心获取的是什么类型的椅子。无论是现代款式的椅子还是维多利亚风格的椅子,客户端都必须以相同的方式对待所有椅子,使用抽象的椅子接口。通过这种方法,客户端只知道椅子以某种方式实现了 sitOn 方法。此外,无论返回哪种椅子变体,它都将与同一工厂对象生产的沙发或咖啡桌类型相匹配。

还有一件需要澄清的事情:如果客户端只暴露给抽象接口,那么是什么创建了实际的工厂对象呢?通常,应用程序在初始化阶段创建具体的工厂对象。在此之前,应用程序必须根据配置或环境设置选择工厂类型。

结构

1、抽象产品为一组独立但相关的产品声明接口,构成一个产品族。

2、具体产品是抽象产品的各种实现,按照变体进行分组。每个抽象产品(椅子/沙发)必须在所有给定的变体(维多利亚风格/现代风格)中进行实现。

3、抽象工厂接口声明了创建每个抽象产品的一组方法。

4、具体工厂实现抽象工厂的创建方法。每个具体工厂对应于特定的产品变体,并只创建相应的产品变体。

5、尽管具体工厂实例化具体产品,但它们的创建方法的签名必须返回相应的抽象产品。这样,使用工厂的客户端代码不会与工厂返回的具体产品变体耦合。只要通过抽象接口与对象进行通信,客户端就可以与任何具体工厂/产品变体一起工作。

伪代码

这个例子说明了抽象工厂模式如何用于创建跨平台的用户界面元素,而不将客户端代码与具体的UI类耦合,同时保持所有创建的元素与所选操作系统的一致性。

跨平台应用程序中的相同UI元素应该在不同的操作系统下具有相似的行为,但在外观上可能会有些不同。此外,您的任务是确保UI元素与当前操作系统的风格匹配。当在Windows操作系统中执行时,您不希望程序渲染macOS控件。

抽象工厂接口声明了一组创建方法,客户端代码可以使用这些方法来生成不同类型的UI元素。具体工厂对应于特定的操作系统,并创建与该操作系统相匹配的UI元素。

它的工作原理是:当应用程序启动时,它会检查当前操作系统的类型。应用程序使用这些信息来创建一个与操作系统匹配的工厂对象。其余的代码使用该工厂来创建UI元素。这样可以防止创建错误的元素。

通过这种方法,只要客户端代码通过抽象接口与工厂和UI元素一起使用,它就不依赖于具体的工厂和UI元素类。这也让客户端代码能够支持您未来可能添加的其他工厂或UI元素。

因此,您无需每次向应用程序添加新的UI元素变体时修改客户端代码。您只需创建一个新的工厂类来生成这些元素,并稍微修改应用程序的初始化代码,以便在适当的时候选择该类。

// The abstract factory interface declares a set of methods that // return different abstract products. These products are called // a family and are related by a high-level theme or concept. // Products of one family are usually able to collaborate among // themselves. A family of products may have several variants, // but the products of one variant are incompatible with the // products of another variant. interface GUIFactory is method createButton():Button method createCheckbox():Checkbox // Concrete factories produce a family of products that belong // to a single variant. The factory guarantees that the // resulting products are compatible. Signatures of the concrete // factory's methods return an abstract product, while inside // the method a concrete product is instantiated. class WinFactory implements GUIFactory is method createButton():Button is return new WinButton() method createCheckbox():Checkbox is return new WinCheckbox() // Each concrete factory has a corresponding product variant. class MacFactory implements GUIFactory is method createButton():Button is return new MacButton() method createCheckbox():Checkbox is return new MacCheckbox() // Each distinct product of a product family should have a base // interface. All variants of the product must implement this // interface. interface Button is method paint() // Concrete products are created by corresponding concrete // factories. class WinButton implements Button is method paint() is // Render a button in Windows style. class MacButton implements Button is method paint() is // Render a button in macOS style. // Here's the base interface of another product. All products // can interact with each other, but proper interaction is // possible only between products of the same concrete variant. interface Checkbox is method paint() class WinCheckbox implements Checkbox is method paint() is // Render a checkbox in Windows style. class MacCheckbox implements Checkbox is method paint() is // Render a checkbox in macOS style. // The client code works with factories and products only // through abstract types: GUIFactory, Button and Checkbox. This // lets you pass any factory or product subclass to the client // code without breaking it. class Application is private field factory: GUIFactory private field button: Button constructor Application(factory: GUIFactory) is this.factory = factory method createUI() is this.button = factory.createButton() method paint() is button.paint() // The application picks the factory type depending on the // current configuration or environment settings and creates it // at runtime (usually at the initialization stage). class ApplicationConfigurator is method main() is config = readApplicationConfigFile() if (config.OS == "Windows") then factory = new WinFactory() else if (config.OS == "Mac") then factory = new MacFactory() else throw new Exception("Error! Unknown operating system.") Application app = new Application(factory)适用性

1、当代码需要处理不同系列相关产品,但又不希望依赖这些产品的具体类(可能事先未知或者希望允许未来的扩展),可以使用抽象工厂模式。

抽象工厂为每个产品系列的类提供了一个创建对象的接口。只要代码通过这个接口创建对象,就不必担心创建与应用程序已创建的产品不匹配的错误变体。

2、考虑在一个类中实现抽象工厂时,如果这个类有一组工厂方法,这些方法可能会模糊其主要责任。

在设计良好的程序中,每个类只负责一件事。当一个类处理多种产品类型时,值得将其工厂方法提取到一个独立的工厂类或完整的抽象工厂实现中。

如何实现

根据产品类型和产品的各个变体,绘制出一个矩阵。

为所有产品类型声明抽象产品接口。然后让所有具体产品类实现这些接口。

为所有抽象产品声明抽象工厂接口,其中包含一组创建方法。

实现一组具体工厂类,每个工厂类对应一个产品变体。

在应用程序中的某个地方创建工厂初始化代码。根据应用程序的配置或当前环境,它应该实例化其中一个具体工厂类。将此工厂对象传递给所有构建产品的类。

扫描代码,找到所有直接调用产品构造函数的地方。将它们替换为对工厂对象上适当的创建方法的调用。

Python示例

from __future__ import annotations from abc import ABC, abstractmethod class AbstractFactory(ABC): """ The Abstract Factory interface declares a set of methods that return different abstract products. These products are called a family and are related by a high-level theme or concept. Products of one family are usually able to collaborate among themselves. A family of products may have several variants, but the products of one variant are incompatible with products of another. """ @abstractmethod def create_product_a(self) -> AbstractProductA: pass @abstractmethod def create_product_b(self) -> AbstractProductB: pass class ConcreteFactory1(AbstractFactory): """ Concrete Factories produce a family of products that belong to a single variant. The factory guarantees that resulting products are compatible. Note that signatures of the Concrete Factory's methods return an abstract product, while inside the method a concrete product is instantiated. """ def create_product_a(self) -> AbstractProductA: return ConcreteProductA1() def create_product_b(self) -> AbstractProductB: return ConcreteProductB1() class ConcreteFactory2(AbstractFactory): """ Each Concrete Factory has a corresponding product variant. """ def create_product_a(self) -> AbstractProductA: return ConcreteProductA2() def create_product_b(self) -> AbstractProductB: return ConcreteProductB2() class AbstractProductA(ABC): """ Each distinct product of a product family should have a base interface. All variants of the product must implement this interface. """ @abstractmethod def useful_function_a(self) -> str: pass """ Concrete Products are created by corresponding Concrete Factories. """ class ConcreteProductA1(AbstractProductA): def useful_function_a(self) -> str: return "The result of the product A1." class ConcreteProductA2(AbstractProductA): def useful_function_a(self) -> str: return "The result of the product A2." class AbstractProductB(ABC): """ Here's the the base interface of another product. All products can interact with each other, but proper interaction is possible only between products of the same concrete variant. """ @abstractmethod def useful_function_b(self) -> None: """ Product B is able to do its own thing... """ pass @abstractmethod def another_useful_function_b(self, collaborator: AbstractProductA) -> None: """ ...but it also can collaborate with the ProductA. The Abstract Factory makes sure that all products it creates are of the same variant and thus, compatible. """ pass """ Concrete Products are created by corresponding Concrete Factories. """ class ConcreteProductB1(AbstractProductB): def useful_function_b(self) -> str: return "The result of the product B1." """ The variant, Product B1, is only able to work correctly with the variant, Product A1. Nevertheless, it accepts any instance of AbstractProductA as an argument. """ def another_useful_function_b(self, collaborator: AbstractProductA) -> str: result = collaborator.useful_function_a() return f"The result of the B1 collaborating with the ({result})" class ConcreteProductB2(AbstractProductB): def useful_function_b(self) -> str: return "The result of the product B2." def another_useful_function_b(self, collaborator: AbstractProductA): """ The variant, Product B2, is only able to work correctly with the variant, Product A2. Nevertheless, it accepts any instance of AbstractProductA as an argument. """ result = collaborator.useful_function_a() return f"The result of the B2 collaborating with the ({result})" def client_code(factory: AbstractFactory) -> None: """ The client code works with factories and products only through abstract types: AbstractFactory and AbstractProduct. This lets you pass any factory or product subclass to the client code without breaking it. """ product_a = factory.create_product_a() product_b = factory.create_product_b() print(f"{product_b.useful_function_b()}") print(f"{product_b.another_useful_function_b(product_a)}", end="") if __name__ == "__main__": """ The client code can work with any concrete factory class. """ print("Client: Testing client code with the first factory type:") client_code(ConcreteFactory1()) print("\n") print("Client: Testing the same client code with the second factory type:") client_code(ConcreteFactory2())

查看全文
大家还看了
也许喜欢
更多游戏

Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved