2. 创建型模式 - 抽象工厂模式

news2025/2/5 14:49:47

亦称: Abstract Factory

 意图

抽象工厂模式是一种创建型设计模式, 它能创建一系列相关的对象, 而无需指定其具体类。

抽象工厂模式

 问题

假设你正在开发一款家具商店模拟器。 你的代码中包括一些类, 用于表示:

  1. 一系列相关产品, 例如 椅子Chair 、 ​ 沙发Sofa和 咖啡桌Coffee­Table 。

  2. 系列产品的不同变体。 例如, 你可以使用 现代Modern 、 ​ 维多利亚Victorian 、 ​ 装饰风艺术Art­Deco等风格生成 椅子 、 ​ 沙发和 咖啡桌 。

生成不同风格的系列家具。

系列产品及其不同变体。

你需要设法单独生成每件家具对象, 这样才能确保其风格一致。 如果顾客收到的家具风格不一样, 他们可不会开心。

现代风格的沙发和维多利亚风格的椅子不搭。

此外, 你也不希望在添加新产品或新风格时修改已有代码。 家具供应商对于产品目录的更新非常频繁, 你不会想在每次更新时都去修改核心代码的。

 解决方案

首先, 抽象工厂模式建议为系列中的每件产品明确声明接口 (例如椅子、 沙发或咖啡桌)。 然后, 确保所有产品变体都继承这些接口。 例如, 所有风格的椅子都实现 椅子接口; 所有风格的咖啡桌都实现 咖啡桌接口, 以此类推。

椅子类的层次结构

同一对象的所有变体都必须放置在同一个类层次结构之中。

接下来, 我们需要声明抽象工厂——包含系列中所有产品构造方法的接口。 例如 create­Chair创建椅子 、 ​ create­Sofa创建沙发和 create­Coffee­Table创建咖啡桌 。 这些方法必须返回抽象产品类型, 即我们之前抽取的那些接口: ​ 椅子 , ​ 沙发和 咖啡桌等等。

工厂类的层次结构

每个具体工厂类都对应一个特定的产品变体。

那么该如何处理产品变体呢? 对于系列产品的每个变体, 我们都将基于 抽象工厂接口创建不同的工厂类。 每个工厂类都只能返回特定类别的产品, 例如, ​ 现代家具工厂Modern­Furniture­Factory只能创建 现代椅子Modern­Chair 、 ​ 现代沙发Modern­Sofa和 现代咖啡桌Modern­Coffee­Table对象。

客户端代码可以通过相应的抽象接口调用工厂和产品类。 你无需修改实际客户端代码, 就能更改传递给客户端的工厂类, 也能更改客户端代码接收的产品变体。

客户端无需了解其所调用工厂的具体类信息。

假设客户端想要工厂创建一把椅子。 客户端无需了解工厂类, 也不用管工厂类创建出的椅子类型。 无论是现代风格, 还是维多利亚风格的椅子, 对于客户端来说没有分别, 它只需调用抽象 椅子接口就可以了。 这样一来, 客户端只需知道椅子以某种方式实现了 sit­On坐下方法就足够了。 此外, 无论工厂返回的是何种椅子变体, 它都会和由同一工厂对象创建的沙发或咖啡桌风格一致。

最后一点说明: 如果客户端仅接触抽象接口, 那么谁来创建实际的工厂对象呢? 一般情况下, 应用程序会在初始化阶段创建具体工厂对象。 而在此之前, 应用程序必须根据配置文件或环境设定选择工厂类别。

 抽象工厂模式结构

抽象工厂设计模式

  1. 抽象产品 (Abstract Product) 为构成系列产品的一组不同但相关的产品声明接口。

  2. 具体产品 (Concrete Product) 是抽象产品的多种不同类型实现。 所有变体 (维多利亚/现代) 都必须实现相应的抽象产品 (椅子/沙发)。

  3. 抽象工厂 (Abstract Factory) 接口声明了一组创建各种抽象产品的方法。

  4. 具体工厂 (Concrete Factory) 实现抽象工厂的构建方法。 每个具体工厂都对应特定产品变体, 且仅创建此种产品变体。

  5. 尽管具体工厂会对具体产品进行初始化, 其构建方法签名必须返回相应的抽象产品。 这样, 使用工厂类的客户端代码就不会与工厂创建的特定产品变体耦合。 客户端 (Client) 只需通过抽象接口调用工厂和产品对象, 就能与任何具体工厂/产品变体交互。

 伪代码

下面例子通过应用抽象工厂模式, 使得客户端代码无需与具体 UI 类耦合, 就能创建跨平台的 UI 元素, 同时确保所创建的元素与指定的操作系统匹配。

抽象工厂模式示例的类图

跨平台 UI 类示例。

跨平台应用中的相同 UI 元素功能类似, 但是在不同操作系统下的外观有一定差异。 此外, 你需要确保 UI 元素与当前操作系统风格一致。 你一定不希望在 Windows 系统下运行的应用程序中显示 macOS 的控件。

抽象工厂接口声明一系列构建方法, 客户端代码可调用它们生成不同风格的 UI 元素。 每个具体工厂对应特定操作系统, 并负责生成符合该操作系统风格的 UI 元素。

其运作方式如下: 应用程序启动后检测当前操作系统。 根据该信息, 应用程序通过与该操作系统对应的类创建工厂对象。 其余代码使用该工厂对象创建 UI 元素。 这样可以避免生成错误类型的元素。

使用这种方法, 客户端代码只需调用抽象接口, 而无需了解具体工厂类和 UI 元素。 此外, 客户端代码还支持未来添加新的工厂或 UI 元素。

这样一来, 每次在应用程序中添加新的 UI 元素变体时, 你都无需修改客户端代码。 你只需创建一个能够生成这些 UI 元素的工厂类, 然后稍微修改应用程序的初始代码, 使其能够选择合适的工厂类即可。

// 抽象工厂接口声明了一组能返回不同抽象产品的方法。这些产品属于同一个系列
// 且在高层主题或概念上具有相关性。同系列的产品通常能相互搭配使用。系列产
// 品可有多个变体,但不同变体的产品不能搭配使用。
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)

 抽象工厂模式适合应用场景

 如果代码需要与多个不同系列的相关产品交互, 但是由于无法提前获取相关信息, 或者出于对未来扩展性的考虑, 你不希望代码基于产品的具体类进行构建, 在这种情况下, 你可以使用抽象工厂。

 抽象工厂为你提供了一个接口, 可用于创建每个系列产品的对象。 只要代码通过该接口创建对象, 那么你就不会生成与应用程序已生成的产品类型不一致的产品。

 如果你有一个基于一组抽象方法的类, 且其主要功能因此变得不明确, 那么在这种情况下可以考虑使用抽象工厂模式。

 在设计良好的程序中, 每个类仅负责一件事。 如果一个类与多种类型产品交互, 就可以考虑将工厂方法抽取到独立的工厂类或具备完整功能的抽象工厂类中。

 实现方式

  1. 以不同的产品类型与产品变体为维度绘制矩阵。

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

  3. 声明抽象工厂接口, 并且在接口中为所有抽象产品提供一组构建方法。

  4. 为每种产品变体实现一个具体工厂类。

  5. 在应用程序中开发初始化代码。 该代码根据应用程序配置或当前环境, 对特定具体工厂类进行初始化。 然后将该工厂对象传递给所有需要创建产品的类。

  6. 找出代码中所有对产品构造函数的直接调用, 将其替换为对工厂对象中相应构建方法的调用。

 抽象工厂模式优缺点

  •  你可以确保同一工厂生成的产品相互匹配。
  •  你可以避免客户端和具体产品代码的耦合。
  •  单一职责原则。 你可以将产品生成代码抽取到同一位置, 使得代码易于维护。
  •  开闭原则。 向应用程序中引入新产品变体时, 你无需修改客户端代码。
  •  由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂。

 与其他模式的关系

  • ​在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。

  • 生成器重点关注如何分步生成复杂对象。 抽象工厂专门用于生产一系列相关对象。 抽象工厂会马上返回产品, 生成器则允许你在获取产品前执行一些额外构造步骤。

  • 抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。

  • 当只需对客户端代码隐藏子系统创建对象的方式时,

  • 你可以使用抽象工厂来代替外观模式。 你可以将抽象工厂和桥接模式搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。

  • 抽象工厂、 生成器和原型都可以用单例模式来实现。 ​

 代码示例

/**
 * Each distinct product of a product family should have a base interface. All
 * variants of the product must implement this interface.
 */
class AbstractProductA {
 public:
  virtual ~AbstractProductA(){};
  virtual std::string UsefulFunctionA() const = 0;
};

/**
 * Concrete Products are created by corresponding Concrete Factories.
 */
class ConcreteProductA1 : public AbstractProductA {
 public:
  std::string UsefulFunctionA() const override {
    return "The result of the product A1.";
  }
};

class ConcreteProductA2 : public AbstractProductA {
  std::string UsefulFunctionA() const override {
    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.
 */
class AbstractProductB {
  /**
   * Product B is able to do its own thing...
   */
 public:
  virtual ~AbstractProductB(){};
  virtual std::string UsefulFunctionB() const = 0;
  /**
   * ...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.
   */
  virtual std::string AnotherUsefulFunctionB(const AbstractProductA &collaborator) const = 0;
};

/**
 * Concrete Products are created by corresponding Concrete Factories.
 */
class ConcreteProductB1 : public AbstractProductB {
 public:
  std::string UsefulFunctionB() const override {
    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.
   */
  std::string AnotherUsefulFunctionB(const AbstractProductA &collaborator) const override {
    const std::string result = collaborator.UsefulFunctionA();
    return "The result of the B1 collaborating with ( " + result + " )";
  }
};

class ConcreteProductB2 : public AbstractProductB {
 public:
  std::string UsefulFunctionB() const override {
    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.
   */
  std::string AnotherUsefulFunctionB(const AbstractProductA &collaborator) const override {
    const std::string result = collaborator.UsefulFunctionA();
    return "The result of the B2 collaborating with ( " + result + " )";
  }
};

/**
 * 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.
 */
class AbstractFactory {
 public:
  virtual AbstractProductA *CreateProductA() const = 0;
  virtual AbstractProductB *CreateProductB() const = 0;
};

/**
 * 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 : public AbstractFactory {
 public:
  AbstractProductA *CreateProductA() const override {
    return new ConcreteProductA1();
  }
  AbstractProductB *CreateProductB() const override {
    return new ConcreteProductB1();
  }
};

/**
 * Each Concrete Factory has a corresponding product variant.
 */
class ConcreteFactory2 : public AbstractFactory {
 public:
  AbstractProductA *CreateProductA() const override {
    return new ConcreteProductA2();
  }
  AbstractProductB *CreateProductB() const override {
    return new ConcreteProductB2();
  }
};

/**
 * 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.
 */

void ClientCode(const AbstractFactory &factory) {
  const AbstractProductA *product_a = factory.CreateProductA();
  const AbstractProductB *product_b = factory.CreateProductB();
  std::cout << product_b->UsefulFunctionB() << "\n";
  std::cout << product_b->AnotherUsefulFunctionB(*product_a) << "\n";
  delete product_a;
  delete product_b;
}

int main() {
  std::cout << "Client: Testing client code with the first factory type:\n";
  ConcreteFactory1 *f1 = new ConcreteFactory1();
  ClientCode(*f1);
  delete f1;
  std::cout << std::endl;
  std::cout << "Client: Testing the same client code with the second factory type:\n";
  ConcreteFactory2 *f2 = new ConcreteFactory2();
  ClientCode(*f2);
  delete f2;
  return 0;
}
执行结果
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.)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1328178.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

竞赛保研 基于CNN实现谣言检测 - python 深度学习 机器学习

文章目录 1 前言1.1 背景 2 数据集3 实现过程4 CNN网络实现5 模型训练部分6 模型评估7 预测结果8 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于CNN实现谣言检测 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&am…

【自学笔记】Python测试框架pytest

文章目录 📖 介绍 📖🏡 环境 🏡📒 使用方法 📒📝 安装📝 编写测试函数⚓️ 相关链接 ⚓️📖 介绍 📖 pytest 是一个广泛使用的 Python 测试框架,它提供了简单、灵活和可扩展的测试功能。通过 pytest,您可以轻松地创建和运行单元测试、集成测试和功能测试…

专业数据中台哪个好?亿发数字化运营平台解决方案,助力大中型企业腾飞

数据中台的核心在于避免数据的重复计算&#xff0c;通过数据服务化的方式提升数据的共享能力&#xff0c;为数据应用赋能。 在信息技术时代&#xff0c;企业的信息系统建设通常是由各个组织和功能单元独立完成&#xff0c;以满足各自的需求。这导致了“数据孤岛”和“数据烟囱”…

整数比较(比较4个数并从小到大输出)C语言xdoj94

描述&#xff1a; 从键盘输入四个整数&#xff0c;要求按由小到大的顺序输出。 输入说明&#xff1a; 输入四个整数&#xff0c;以空格间隔。 输出说明&#xff1a; 输出排序后的整数&#xff0c;以空格间隔。 输入样例 样例1输入 -99 9 99 -9 输出样例 样例1输出 -99 -9 9 99 …

开源 AI 新秀崛起:Bittensor 更像是真正的“OpenAI”

强大的人工智能正在飞速发展&#xff0c;而完全由 OpenAI、Midjourney、Google&#xff08;Bard&#xff09;这样的少数公司控制 AI 不免让人感到担忧。在这样的背景下&#xff0c;试图用创新性解决方案处理人工智能中心化问题、权力集中于少数公司的 Bittensor&#xff0c;可谓…

软件渗透测试有哪些测试流程?权威安全测试报告的重要性

软件渗透测试也是安全测试的一种&#xff0c;是通过模拟恶意黑客的攻击方法&#xff0c;来评估计算机网络系统安全的一种评估方法。作为网络安全防范的一种新技术&#xff0c;对于网络安全组织具有实际应用价值。 一、软件渗透测试的过程   软件渗透测试的过程通常包括四个主…

毕业“寄” 划算,享闪侠惠递巨额优惠福利,助你轻装向未来!

毕业季&#xff0c;你是否也为“行李难题”而烦恼&#xff01;别着急额&#xff0c;闪侠惠递来帮你&#xff0c;让您轻松寄快递&#xff0c;省时省力省心回家&#xff01; 毕业季即将来临&#xff0c;相信大家已经开始为苦读一年的自己计划一个美好的寒假暑期。而在期待假期的…

程序员自由创业周记#20:需求从何而来

程序员自由创业周记#20&#xff1a;需求从何而来 之前看过我周记的朋友应该了解我从7月份开始独立创业以来&#xff0c;主要做了两个产品&#xff0c;一个是加一&#xff0c;一个是Island Widgets - 灵动岛锁屏小组件 &#xff0c;上班的时候工作内容是上级主管分配&#xff0c…

算法与数据结构--特殊有序集的线性时间排序算法

一.计数排序算法 基本思想&#xff1a;统计每个输入元素的个数&#xff0c;然后根据这些计数值重构原数组。 使用范围&#xff1a;需要知道元素大小范围&#xff0c;就是最大值是多少。 【排序算法】计数排序_哔哩哔哩_bilibili 二.基数排序 使用场景&#xff1a;只适用于…

Java 微服务框架 HP-SOA v1.0.6 发布 — 完整支持 Dubbo 和 Spring Cloud

HP-SOA 功能完备&#xff0c;简单易用&#xff0c;高度可扩展的Java微服务框架。 项目主页 : https://www.oschina.net/p/hp-soa下载地址 : https://github.com/ldcsaa/hp-soa开发文档 : https://gitee.com/ldcsaa/hp-soa/blob/master/README.mdQQ Group: 44636872, 66390394…

前端vue keyframes动画

8.1.2 keyframes动画 transition动画适合用来创建简单的过渡效果。CSS3中支持使用animation属性来配置更加复杂的动画效果。animation属性根据keyframes配置来执行基于关键帧的动画效果。新建一个名为keyframes.vue的测试文件。编写如下代码&#xff1a; <template><…

如何实现设备远程控制?

在工业自动化领域&#xff0c;设备远程控制是一项非常重要的技术。它使得设备可以在远离现场的情况下进行远程操作和维护&#xff0c;大大提高了设备的可用性和效率。 设备远程控制的应用场景有哪些&#xff1f; 远程故障排除&#xff1a;当设备出现故障时&#xff0c;工程师…

CH06_访问数据结构

Visitor 模式 访问者模式&#xff08;Visitor&#xff09;&#xff0c;表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的提前下定义作用于这些元素的新操作。 类图 说明 Visitor&#xff08;访问者&#xff09; Visitor角色负责对数据结构中每一个…

[CVPR 2023:3D Gaussian Splatting:实时的神经场渲染]

文章目录 前言小结 原文地址&#xff1a;https://blog.csdn.net/qq_45752541/article/details/132854115 前言 mesh 和点是最常见的3D场景表示&#xff0c;因为它们是显式的&#xff0c;非常适合于快速的基于GPU/CUDA的栅格化。相比之下&#xff0c;最近的神经辐射场&#xf…

Leaning Method

001用分布在两个地方的两台办公电脑开发一个项目&#xff0c;计划使用gitee同步代码。具体应该怎么操作&#xff1f; 要使用 Gitee 同步代码&#xff0c;你可以按照以下步骤进行操作&#xff1a; 在两台办公电脑上都安装 Git 客户端&#xff0c;并在 Gitee 上创建一个项目仓库…

众和策略:航运板块强势拉升,海通发展涨停,宁波远洋等走高

航运板块22日盘中强势拉升&#xff0c;截至发稿&#xff0c;海通开展涨停&#xff0c;宁波远洋、锦江航运涨约6%&#xff0c;中远海特、招商南油涨近4%。 音讯面上&#xff0c;近来&#xff0c;多艘船舶在红海水域遭也门胡塞装备突击。为规避风险&#xff0c;数家国际航运企业…

外汇天眼:我碰到外汇投资骗局了吗?学会这5招,轻松识别外汇诈骗黑平台!

近年来外汇市场因为交易量大、流动性大、不容易被控盘、品种简单、风险相对低等特色&#xff0c;因此吸引不少投资人青睐&#xff0c;成为全球金融市场的热门选择。 然而&#xff0c;市面上充斥许多诈骗集团设立的黑平台&#xff0c;也打着投资外汇的名义行骗&#xff0c;不免会…

PHP 读取excel输入为HTML

目录 介绍 安装扩展 读取excel文件 输入为html 保存到文件 总结 介绍 以前都是使用phpexcel&#xff0c;不过已经不再更新了&#xff0c; 不过不用担心还可以使用phpspreadsheet来替代它进行操作。 PHPSpreadsheet-在PHP中读取、创建和编写电子表格文档-电子表格引擎。…

Instagram账号被封?必须了解的原因与防封技巧

您是否曾因 Instagram 帐户被暂停封禁而感到沮丧&#xff1f;这是一个常见问题&#xff0c;了解您的帐户被暂停的原因以及如何恢复帐户至关重要。 在本文中&#xff0c;我们将深入探讨 Instagram 帐户被封停的常见原因&#xff0c;并探讨重新获得访问权限的步骤。这个方法对于管…

55.0/CSS 的应用(详细版)

目录 55.1.1 设计边框样式 55.1.2 调整边框的粗细 55.1.3 边框颜色 55.1.4 复合设置边框 55.2 模块的边距 55.3 模块的内边距 55.4 层的应用 55.4.1 层的建立 55.4.2 浮动——float 55.4.3 清除浮动 55.4.4 层的定位 55.4.5 设置层的溢出——overflow 55.4.6 设置鼠…