设计模式
设计模式简介
看懂UML类图和时序图
UML统一建模语言
UML类图及类图之间的关系
类关系记忆技巧
如何正确使用设计模式
优秀设计的特征
面向对象设计原则
创建型设计模式
工厂模式
抽象工厂模式
简单工厂模式
静态工厂模式(Static Factory)
单例模式
建造者模式
原型模式
结构型设计模式
适配器模式
桥接模式
组合模式
装饰器模式
外观模式
享元模式
代理模式
过滤器模式
注册模式(Registry)
行为型设计模式
责任链模式
命令模式
解释器模式
中介者模式
备忘录模式
迭代器模式
观察者模式
状态模式
策略模式
模板模式
访问者模式
规格模式(Specification)
J2EE 设计模式
MVC 模式
业务代表模式
组合实体模式
数据访问对象模式(DAO模式)
前端控制器模式
拦截过滤器模式
空对象模式
服务定位器模式
传输对象模式
数据映射模式(Data Mapper)
依赖注入模式(Dependency Injection)
流接口模式(Fluent Interface)
其他模式
对象池模式(Pool)
委托模式
资源库模式(Repository)
实体属性值模式(EAV 模式)
反面模式
归纳设计模式
本文档使用 MrDoc 发布
-
+
首页
抽象工厂模式
> 抽象工厂模式是一种创建型设计模式, 它能创建一系列相关的对象, 而无需指定其具体类。 ![](/media/202203/2022-03-05_190146_633522.png) 抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。 ## 目的 在不指定具体类的情况下创建一系列相关或依赖对象。 通常创建的类都实现相同的接口。 抽象工厂的客户并不关心这些对象是如何创建的,它只是知道它们是如何一起运行的。 ## 介绍 - 意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。 - 主要解决:主要解决接口选择的问题。 - 何时使用:系统的产品有**多于一个的产品族**,而系统只消费其中某一族的产品。 - 如何解决:在一个产品族里面,定义多个产品。 - 关键代码:在一个工厂里聚合多个同类产品。 - 应用实例:工作了,为了参加一些聚会,肯定有两套或多套衣服吧,比如说有商务装、时尚装,甚至对于一个家庭来说,可能有商务女装、商务男装、时尚女装、时尚男装,这些也都是成套的,即一系列具体产品。假设一种情况,在您的家中,某一个衣柜(具体工厂)只能存放某一种这样的衣服(成套,一系列具体产品),每次拿这种成套的衣服时也自然要从这个衣柜中取出了。用 OOP 的思想去理解,所有的衣柜(具体工厂)都是衣柜类的(抽象工厂)某一个,而每一件成套的衣服又包括具体的上衣(某一具体产品),裤子(某一具体产品),这些具体的上衣其实也都是上衣(抽象产品),具体的裤子也都是裤子(另一个抽象产品)。 - 优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。 - 缺点:产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。 - 使用场景: 1、QQ 换皮肤,一整套一起换。 2、生成不同操作系统的程序。 - 注意事项:产品族难扩展,产品等级易扩展。 ## 结构 ![抽象工厂模式结构](/media/202202/2022-02-27_134323_166038.png) - 抽象产品 (Abstract Product)为构成系列产品的一组不同但相关的产品声明接口。 - 具体产品 (Concrete Product)是抽象产品的多种不同类型实现。 所有变体都必须实现相应的抽象产品。 - 抽象工厂 (Abstract Factory)接口声明了一组创建各种抽象产品的方法。 - 具体工厂 (Concrete Factory) 实现抽象工厂的构建方法。 每个具体工厂都对应特定产品变体, 且仅创建此种产品变体。 - 尽管具体工厂会对具体产品进行初始化, 其构建方法签名必须返回相应的抽象产品。 这样, 使用工厂类的客户端代码就不会与工厂创建的特定产品变体耦合。 客户端 (Client) 只需通过抽象接口调用工厂和产品对象, 就能与任何具体工厂/产品变体交互。 ## UML ![抽象工厂模式结构](/media/202203/2022-03-20_211922_824235.png) ## 伪代码 下面例子通过应用抽象工厂模式, 使得客户端代码无需与具体 UI 类耦合, 就能创建跨平台的 UI 元素, 同时确保所创建的元素与指定的操作系统匹配。 ![跨平台 UI 类示例](/media/202202/2022-02-27_135542_970925.png) 跨平台应用中的相同 UI 元素功能类似, 但是在不同操作系统下的外观有一定差异。 此外, 你需要确保 UI 元素与当前操作系统风格一致。 你一定不希望在 Windows 系统下运行的应用程序中显示 macOS 的控件。 抽象工厂接口声明一系列构建方法, 客户端代码可调用它们生成不同风格的 UI 元素。 每个具体工厂对应特定操作系统, 并负责生成符合该操作系统风格的 UI 元素。 其运作方式如下: 应用程序启动后检测当前操作系统。 根据该信息, 应用程序通过与该操作系统对应的类创建工厂对象。 其余代码使用该工厂对象创建 UI 元素。 这样可以避免生成错误类型的元素。 使用这种方法, 客户端代码只需调用抽象接口, 而无需了解具体工厂类和 UI 元素。 此外, 客户端代码还支持未来添加新的工厂或 UI 元素。 这样一来, 每次在应用程序中添加新的 UI 元素变体时, 你都无需修改客户端代码。 你只需创建一个能够生成这些 UI 元素的工厂类, 然后稍微修改应用程序的初始代码, 使其能够选择合适的工厂类即可。 ```java // 抽象工厂接口声明了一组能返回不同抽象产品的方法。这些产品属于同一个系列 // 且在高层主题或概念上具有相关性。同系列的产品通常能相互搭配使用。系列产 // 品可有多个变体,但不同变体的产品不能搭配使用。 interface GUIFactory is method createButton():Button method createCheckbox():Checkbox // 具体工厂可生成属于同一变体的系列产品。工厂会确保其创建的产品能相互搭配 // 使用。具体工厂方法签名会返回一个抽象产品,但在方法内部则会对具体产品进 // 行实例化。 class WinFactory implements GUIFactory is method createButton():Button is return new WinButton() method createCheckbox():Checkbox is return new WinCheckbox() // 每个具体工厂中都会包含一个相应的产品变体。 class MacFactory implements GUIFactory is method createButton():Button is return new MacButton() method createCheckbox():Checkbox is return new MacCheckbox() // 系列产品中的特定产品必须有一个基础接口。所有产品变体都必须实现这个接口。 interface Button is method paint() // 具体产品由相应的具体工厂创建。 class WinButton implements Button is method paint() is // 根据 Windows 样式渲染按钮。 class MacButton implements Button is method paint() is // 根据 macOS 样式渲染按钮 // 这是另一个产品的基础接口。所有产品都可以互动,但是只有相同具体变体的产 // 品之间才能够正确地进行交互。 interface Checkbox is method paint() class WinCheckbox implements Checkbox is method paint() is // 根据 Windows 样式渲染复选框。 class MacCheckbox implements Checkbox is method paint() is // 根据 macOS 样式渲染复选框。 // 客户端代码仅通过抽象类型(GUIFactory、Button 和 Checkbox)使用工厂 // 和产品。这让你无需修改任何工厂或产品子类就能将其传递给客户端代码。 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() // 程序会根据当前配置或环境设定选择工厂类型,并在运行时创建工厂(通常在初 // 始化阶段)。 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("错误!未知的操作系统。") Application app = new Application(factory) ``` ## 应用场景 如果代码需要与多个不同系列的相关产品交互, 但是由于无法提前获取相关信息, 或者出于对未来扩展性的考虑, 你不希望代码基于产品的具体类进行构建, 在这种情况下, 你可以使用抽象工厂。 抽象工厂为你提供了一个接口, 可用于创建每个系列产品的对象。 只要代码通过该接口创建对象, 那么你就不会生成与应用程序已生成的产品类型不一致的产品。 如果你有一个基于一组抽象方法的类, 且其主要功能因此变得不明确, 那么在这种情况下可以考虑使用抽象工厂模式。 在设计良好的程序中, 每个类仅负责一件事。 如果一个类与多种类型产品交互, 就可以考虑将工厂方法抽取到独立的工厂类或具备完整功能的抽象工厂类中。 ## 实现方式 以不同的产品类型与产品变体为维度绘制矩阵。 为所有产品声明抽象产品接口。 然后让所有具体产品类实现这些接口。 声明抽象工厂接口, 并且在接口中为所有抽象产品提供一组构建方法。 为每种产品变体实现一个具体工厂类。 在应用程序中开发初始化代码。 该代码根据应用程序配置或当前环境, 对特定具体工厂类进行初始化。 然后将该工厂对象传递给所有需要创建产品的类。 找出代码中所有对产品构造函数的直接调用, 将其替换为对工厂对象中相应构建方法的调用。 ## 优点 你可以确保同一工厂生成的产品相互匹配。 你可以避免客户端和具体产品代码的耦合。 单一职责原则。 你可以将产品生成代码抽取到同一位置, 使得代码易于维护。 开闭原则。 向应用程序中引入新产品变体时, 你无需修改客户端代码。 ## 缺点 由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂。 ## 与其他模式的关系 在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。 生成器重点关注如何分步生成复杂对象。 抽象工厂专门用于生产一系列相关对象。 抽象工厂会马上返回产品, 生成器则允许你在获取产品前执行一些额外构造步骤。 抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。 当只需对客户端代码隐藏子系统创建对象的方式时, 你可以使用抽象工厂来代替外观模式。 你可以将抽象工厂和桥接模式搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。 抽象工厂、 生成器和原型都可以用单例模式来实现。 ## 代码示例 ### TS ```ts /** * 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. */ interface AbstractFactory { createProductA(): AbstractProductA; createProductB(): AbstractProductB; } /** * 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. */ class ConcreteFactory1 implements AbstractFactory { public createProductA(): AbstractProductA { return new ConcreteProductA1(); } public createProductB(): AbstractProductB { return new ConcreteProductB1(); } } /** * Each Concrete Factory has a corresponding product variant. */ class ConcreteFactory2 implements AbstractFactory { public createProductA(): AbstractProductA { return new ConcreteProductA2(); } public createProductB(): AbstractProductB { return new ConcreteProductB2(); } } /** * Each distinct product of a product family should have a base interface. All * variants of the product must implement this interface. */ interface AbstractProductA { usefulFunctionA(): string; } /** * These Concrete Products are created by corresponding Concrete Factories. */ class ConcreteProductA1 implements AbstractProductA { public usefulFunctionA(): string { return 'The result of the product A1.'; } } class ConcreteProductA2 implements AbstractProductA { public usefulFunctionA(): string { return 'The result of the product A2.'; } } /** * 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. */ interface AbstractProductB { /** * Product B is able to do its own thing... */ usefulFunctionB(): string; /** * ...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. */ anotherUsefulFunctionB(collaborator: AbstractProductA): string; } /** * These Concrete Products are created by corresponding Concrete Factories. */ class ConcreteProductB1 implements AbstractProductB { public usefulFunctionB(): string { 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. */ public anotherUsefulFunctionB(collaborator: AbstractProductA): string { const result = collaborator.usefulFunctionA(); return `The result of the B1 collaborating with the (${result})`; } } class ConcreteProductB2 implements AbstractProductB { public usefulFunctionB(): string { return 'The result of the product B2.'; } /** * 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. */ public anotherUsefulFunctionB(collaborator: AbstractProductA): string { const result = collaborator.usefulFunctionA(); return `The result of the B2 collaborating with the (${result})`; } } /** * 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. */ function clientCode(factory: AbstractFactory) { const productA = factory.createProductA(); const productB = factory.createProductB(); console.log(productB.usefulFunctionB()); console.log(productB.anotherUsefulFunctionB(productA)); } /** * The client code can work with any concrete factory class. */ console.log('Client: Testing client code with the first factory type...'); clientCode(new ConcreteFactory1()); console.log(''); console.log('Client: Testing the same client code with the second factory type...'); clientCode(new ConcreteFactory2()); ``` 输出: ```txt Client: Testing client code with the first factory type... The result of the product B1. The result of the B1 collaborating with the (The result of the product A1.) Client: Testing the same client code with the second factory type... The result of the product B2. The result of the B2 collaborating with the (The result of the product A2.) ```
追风者
2022年3月20日 21:20
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
关于 MrDoc
觅思文档MrDoc
是
州的先生
开发并开源的在线文档系统,其适合作为个人和小型团队的云笔记、文档和知识库管理工具。
如果觅思文档给你或你的团队带来了帮助,欢迎对作者进行一些打赏捐助,这将有力支持作者持续投入精力更新和维护觅思文档,感谢你的捐助!
>>>捐助鸣谢列表
微信
支付宝
QQ
PayPal
Markdown文件
分享
链接
类型
密码
更新密码