抽象工厂
意图
抽象工厂是一种创建设计模式,它允许您生成一系列相关对象,而无需指定具体的类。

问题
假设你正在创造一个家具商店模拟器。你的代码由以下类组成:
一系列相关产品,比如:
椅子
+沙发
+CoffeeTable
.这个家族的几个变种。例如,产品
椅子
+沙发
+CoffeeTable
有以下几种变体:现代
,维多利亚时代
,ArtDeco
.

产品族及其变体。
您需要一种方法来创建单独的家具对象,以便它们与同一家族的其他对象相匹配。当顾客收到不相配的家具时,他们会非常生气。

现代风格的沙发和维多利亚风格的椅子不搭。
此外,在向程序添加新产品或产品系列时,您也不希望更改现有代码。家具供应商经常更新他们的目录,你不希望每次都更改核心代码。
解决方案
抽象工厂模式建议的第一件事是显式地为产品家族中的每个不同产品(例如,椅子、沙发或咖啡桌)声明接口。然后,您可以使所有产品变体遵循这些接口。例如,所有的椅子变体都可以实现椅子
接口;所有咖啡桌变体都可以实现CoffeeTable
接口,等等。

同一对象的所有变量必须移动到单个类层次结构中。
下一个步骤是声明抽象工厂-一个接口,其中包含产品家族中所有产品的创建方法列表(例如,createChair
,createSofa
而且createCoffeeTable
).这些方法必须返回摘要由前面提取的接口表示的产品类型:椅子
,沙发
,CoffeeTable
等等。

每个混凝土工厂对应一个特定的产品变体。
那么产品的变体呢?类为产品族的每个变体创建一个单独的工厂类AbstractFactory
接口。工厂是返回特定类型产品的类。例如,ModernFurnitureFactory
只能创造ModernChair
,ModernSofa
而且ModernCoffeeTable
对象。
客户端代码必须通过工厂和产品各自的抽象接口与它们一起工作。这允许您更改传递给客户端代码的工厂类型,以及客户端代码接收的产品变体,而不会破坏实际的客户端代码。

客户端不应该关心它所使用的工厂的具体类。
假设客户想要一家工厂生产一把椅子。客户不需要知道工厂的类别,也不需要知道它得到什么样的椅子。无论是现代风格的椅子还是维多利亚风格的椅子,客户必须以同样的方式对待所有的椅子,使用抽象椅子
接口。使用这种方法,客户对椅子的唯一了解就是它实现了sitOn
方法。此外,无论退回哪种型号的椅子,它总是与同一工厂生产的沙发或茶几的类型相匹配。
还有一件事需要澄清:如果客户端只对抽象接口公开,那么是什么创建实际的工厂对象?通常,应用程序在初始化阶段创建一个具体的工厂对象。在此之前,应用程序必须根据配置或环境设置选择工厂类型。
结构


抽象的产品为一组不同但相关的产品声明接口,这些产品组成了一个产品族。
具体的产品抽象产品的各种实现,按变量分组。每个抽象产品(椅子/沙发)必须在所有给定的变体(维多利亚/现代)中实现。
的抽象工厂接口声明了一组用于创建每个抽象产品的方法。
具体的工厂实现抽象工厂的创建方法。每个具体工厂都对应于产品的一个特定变体,并且只创建这些产品变体。
虽然具体工厂实例化具体产品,但其创建方法的签名必须返回相应的摘要产品。这样,使用工厂的客户端代码就不会耦合到它从工厂获得的产品的特定变体。的客户端可以使用任何具体的工厂/产品变体,只要它通过抽象接口与它们的对象通信。
伪代码
这个例子说明了如何抽象工厂模式可以用于创建跨平台UI元素,而无需将客户端代码耦合到具体的UI类,同时保持所有创建的元素与所选操作系统一致。

跨平台UI类示例。
跨平台应用程序中的相同UI元素应该表现相似,但在不同的操作系统下看起来略有不同。此外,您的工作是确保UI元素与当前操作系统的风格相匹配。你不希望你的程序在Windows中执行时呈现macOS控件。
抽象工厂接口声明了一组创建方法,客户端代码可以使用这些方法来生成不同类型的UI元素。具体工厂对应于特定的操作系统,并创建与该特定操作系统匹配的UI元素。
它是这样工作的:当应用程序启动时,它检查当前操作系统的类型。应用程序使用这些信息从与操作系统匹配的类中创建一个工厂对象。其余代码使用这个工厂创建UI元素。这可以防止创建错误的元素。
使用这种方法,客户端代码不依赖于工厂和UI元素的具体类,只要它通过抽象接口与这些对象一起工作。这还允许客户端代码支持将来可能添加的其他工厂或UI元素。
因此,你不需要每次添加一个新的UI元素变化到你的应用程序时修改客户端代码。你只需要创建一个新的工厂类来产生这些元素,并稍微修改应用程序的初始化代码,以便在适当的时候选择这个类。
//抽象工厂接口声明了一组方法,返回不同的抽象产品。这些产品被称为“家族”,并由高层次的主题或概念联系在一起。//同一家族的产品通常能够相互协作。一个产品族可能有几个变种,但是一个变种的产品与另一个变种的产品不兼容。接口GUIFactory是方法createButton():按钮方法createCheckbox():Checkbox //混凝土工厂生产的产品系列属于//单一的变体。工厂保证生产出来的产品是兼容的。具体//工厂方法的签名返回一个抽象产品,而在//方法内部,具体产品被实例化。//每个具体工厂都有一个对应的产品变体。类MacFactory实现GUIFactory是方法createButton():按钮是返回新的MacButton()方法createCheckbox():Checkbox是返回新的MacCheckbox() //一个产品族的每个不同的产品都应该有一个基本接口。产品的所有变体都必须实现这个//接口。 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)
适用性
当您的代码需要与各种相关产品族一起工作时,请使用抽象工厂,但您不希望它依赖于这些产品的具体类——它们可能事先未知,或者您只是希望允许未来的可扩展性。
抽象工厂为您提供了一个接口,用于从产品家族的每个类中创建对象。只要你的代码通过这个接口创建对象,你就不必担心创建一个错误的产品变体,因为它与你的应用程序已经创建的产品不匹配。
类的集合时,考虑实现抽象工厂工厂方法这模糊了它的主要责任。
在一个精心设计的程序中每个类只负责一件事.当一个类处理多种产品类型时,可能值得将其工厂方法提取到独立的工厂类或成熟的抽象工厂实现中。
如何实施
绘制出不同产品类型与这些产品的变体的矩阵。
为所有产品类型声明抽象产品接口。然后让所有具体的产品类实现这些接口。
使用一组用于所有抽象产品的创建方法来声明抽象工厂接口。
实现一组具体的工厂类,每个产品变体一个。
在应用程序的某个地方创建工厂初始化代码。它应该实例化一个具体的工厂类,这取决于应用程序配置或当前环境。将这个工厂对象传递给构造产品的所有类。
扫描代码并找到对产品构造函数的所有直接调用。将它们替换为对工厂对象上适当的创建方法的调用。
利与弊
- 你可以确定你从工厂得到的产品是相互兼容的。
- 您可以避免具体产品和客户端代码之间的紧密耦合。
- 单一责任原则.您可以将产品创建代码提取到一个位置,从而使代码更易于支持。
- 打开/关闭原则.您可以在不破坏现有客户端代码的情况下引入新的产品变体。
- 由于在模式中引入了许多新的接口和类,代码可能会变得比它应该的更复杂。
与其他模式的关系
额外的内容
- 阅读我们的工厂比较了解更多关于各种工厂模式和概念之间的差异。