抽象工厂模式(Abstract Factory)

news2024/12/27 22:23:09

定义

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

前言

1. 问题

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

  • 一系列相关产品,例如椅子(Chair)、沙发(Sofa)和咖啡桌(CoffeeTable)
  • 系列产品的不同变体,例如你可以使用现代(Modern)、维多利亚(Victorian)和装饰风艺术(ArtDeco)等风格生成这些产品

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

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

2. 解决方案

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

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

那么该如何处理产品变体呢? 对于系列产品的每个变体, 我们都将基于抽象工厂接口创建不同的工厂类。每个工厂类都只能返回特定类别的产品,例如, 现代家具工厂(ModernFurnitureFactory)只能创建现代椅(ModernChair)、现代沙发(ModernSofa)和现代咖啡桌(ModernCoffeeTable)对象。

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

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

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

结构

  1. 抽象产品(Abstract Product)为构成系列产品的一组不同但相关的产品声明接口。
  2. 具体产品(Concrete Product)是抽象产品的多种不同类型实现。所有变体(维多利亚/现代)都必须实现相应的抽象产品(椅子/沙发)。
  3. 抽象工厂(Abstract Factory)接口声明了一组创建各种抽象产品的方法。
  4. 具体工厂(Concrete Factory)实现抽象工厂的构建方法。每个具体工厂都对应特定产品变体,且仅创建此种产品变体。
  5. 尽管具体工厂会对具体产品进行初始化,其构建方法签名必须返回相应的抽象产品。这样,使用工厂类的客户端代码就不会与工厂创建的特定产品变体耦合。客户端(Client)只需通过抽象接口调用工厂和产品对象,就能与任何具体工厂/产品变体交互。

适用场景

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

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

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

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

实现方式

  1. 以不同的产品类型与产品变体为维度绘制矩阵。
  2. 为所有产品声明抽象产品接口。然后让所有具体产品类实现这些接口。
  3. 声明抽象工厂接口,并且在接口中为所有抽象产品提供一组构建方法。
  4. 为每种产品变体实现一个具体工厂类。
  5. 在应用程序中开发初始化代码。该代码根据应用程序配置或当前环境,对特定具体工厂类进行初始化。然后将该工厂对象传递给所有需要创建产品的类。
  6. 找出代码中所有对产品构造函数的直接调用,将其替换为对工厂对象中相应构建方法的调用。

优点

  • 你可以确保同一工厂生成的产品相互匹配。
  • 你可以避免客户端和具体产品代码的耦合。
  • 单一职责原则。你可以将产品生成代码抽取到同一位置,使得代码易于维护。
  • 开闭原则。向应用程序中引入新产品变体时,你无需修改客户端代码。

缺点

由于采用该模式需要向应用中引入众多接口和类,代码可能会比之前更加复杂。

实例

AbstractFactory.hpp

#ifndef A2AC74A4_E96A_4B51_937C_1E888FCCF4EC
#define A2AC74A4_E96A_4B51_937C_1E888FCCF4EC

#include <memory>
#include "AbstractProduct.hpp"

class Factory{
    public:
        virtual shared_ptr<Movie> productMovie()=0;
        virtual shared_ptr<Book> productBook() = 0;
};

#endif /* A2AC74A4_E96A_4B51_937C_1E888FCCF4EC */

 ConcreteFactory.hpp


#ifndef B6D4A87E_8B55_4433_9446_CD0A4E05F3EB
#define B6D4A87E_8B55_4433_9446_CD0A4E05F3EB

#include "AbstractFactory.hpp"
#include "AbstractProduct.hpp"
#include "ConcreteProduct.hpp"
#include <memory>
using namespace std;

class ChineseProduct: public Factory{
    public:
        shared_ptr<Movie> productMovie() override{
            return make_shared<ChineseMovie>();
        }
        shared_ptr<Book> productBook() override{
            return make_shared<ChineseBook>();
        }
};

class JapaneseProduct : public Factory{
    public:
        shared_ptr<Movie> productMovie()override{
            return make_shared<JapaneseMovie>();
        }
        shared_ptr<Book> productBook() override{
            return make_shared<JapaneseBook>();
        }
};


#endif /* B6D4A87E_8B55_4433_9446_CD0A4E05F3EB */

AbstractProduct.hpp

#ifndef B7A6F158_17F4_48CB_864F_79A6515F2190
#define B7A6F158_17F4_48CB_864F_79A6515F2190

#include <string>
using namespace std; 

class Movie{
    public:
        virtual string showMoiewName() = 0;
};
class Book{
    public :
        virtual string showBookName() = 0;
};
#endif /* B7A6F158_17F4_48CB_864F_79A6515F2190 */

ConcreteProduct.hpp

#ifndef C0BF2BA5_C17A_4007_B403_C5CCB379961C
#define C0BF2BA5_C17A_4007_B403_C5CCB379961C

#include <string>
#include <iostream>
#include "AbstractProduct.hpp"

class ChineseMovie : public Movie {
    string showMoiewName() override {
        return "让子弹飞";
    }
};
class JapaneseMovie : public Movie{
    string showMoiewName() override {
        return "千与千寻";
    }
};

class ChineseBook : public Book{
    string showBookName override {
        return "三国演义";
    }
};
class JapaneseBook : public Book{
    string showBookName() override {
        return "白夜行";
    }
};

#endif /* C0BF2BA5_C17A_4007_B403_C5CCB379961C */

main.cpp

#include "AbstractFactory.hpp"
#include "AbstractProduct.hpp"
#include "ConcreteFactory.hpp"
#include "ConcreteProduct.hpp"
#include "iostream"
using namespace std;
int main(int argc, char const *argv[])
{
    shared_ptr<Factory> factory;
    string conf = "china";

    if(conf == "china"){
        factory = make_shared<ChineseProduct>();
    }else if(conf == "japan"){
        factory = make_shared<JapaneseProduct>();
    }else{
        cout << " ERROR: Unknown" << endl;
    }
    shared_ptr<Movie> movie;
    shared_ptr<Book> book;
    movie = factory->productMovie();
    book = factory->productBook();
    std::cout << "获取一部电影: " << movie->showMoiewName() << std::endl;
    std::cout << "获取一本书: " << book->showBookName() << std::endl;

    return 0;
}

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

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

相关文章

091基于深度学习的手写汉字数字识别含10多种模型

emo仓库和视频演示找091期&#xff1a; 银色子弹zg的个人空间-银色子弹zg个人主页-哔哩哔哩视频 效果展示图如下&#xff1a; 代码文件展示如下&#xff1a; 运行01数据集文本生成制作.py可以读取图片路径保存再txt文本中&#xff0c; 运行02train.py可以对txt文本中的图片路…

同程数科基于 Apache Doris 构建统一实时数仓,查询提速数十倍!

本文导读&#xff1a; 同程数科是同程集团旗下的旅游产业金融科技服务平台&#xff0c;为上下游企业和个人消费者提供数字金融科技服务。近年来&#xff0c;随着同程数科业务的不断拓展和用户量的增加&#xff0c;高效可靠的一站式数据中心建设已成为必不可少的需求。为帮助业…

团体程序设计天梯赛-练习集L2篇④

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;Hello大家好呀&#xff0c;我是陈童学&#xff0c;一个与你一样正在慢慢前行的普通人。 &#x1f3c0;个人主页&#xff1a;陈童学哦CSDN &#x1f4a1;所属专栏&#xff1a;PTA &#x1f381;希望各…

WPF 零基础入门笔记(1):WPF静态页面,布局+样式+触发器

文章目录 官方文档往期回顾零基础笔记项目实战&#xff08;已完结&#xff09; WPF项目创建为什么选net core版本 WPF 静态页面WPF 页面布局WPF样式Style样式行内样式行外样式如果是简单样式&#xff0c;可以这么写如果是复杂样式 WPF样式继承WPF触发器单条件触发器多条件触发 …

LLDP(链路层发现协议)详解及C/C++代码实现

LLDP&#xff08;链路层发现协议&#xff09;是一种IEEE标准协议&#xff08;IEEE 802.1AB&#xff09;&#xff0c;它定义了封装在以太网帧中的消息&#xff0c;目的是通过默认情况下每30秒从每个端口定期重传一次&#xff0c;为设备提供一种向LAN&#xff08;局域网&#xff…

20个Java编程技巧

1. 把字符串常量放在前面 通过把字符串常量放在比较函数equals()比较项的左侧来防止偶然的 NullPointerException 从来都不是一个坏主意&#xff0c;就像这样&#xff1a; 这是毫无疑问的&#xff0c;把一种表达式转换成另一种更好的表达式&#xff0c;并不会失去什么。只要我…

mysql锁机制及MVCC底层原理

一、锁介绍 按性能可分为乐观锁&#xff08;适用于读多写少的情况下&#xff0c;如果是写多&#xff0c;导致过多cpu空转&#xff0c;影响性能&#xff09;和悲观锁&#xff08;适用于写多的情况&#xff09;按数据库操作粒度可分为表锁、页锁、行锁按数据库操作类型可分为读锁…

UE4/5动画系列(1.模板制作)

目录 动画模板制作 同步模板组制作 有模板做什么都方便&#xff0c;所以这里我们做一个动画蓝图的模板&#xff08;动物专用&#xff09; 动画模板制作 第一步创建一个动画蓝图的模板 然后找到第三人称的模板&#xff0c;将其模板的蓝图改名&#xff1a; 在动画蓝图的模板里…

团体程序设计天梯赛-练习集L2篇②

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;Hello大家好呀&#xff0c;我是陈童学&#xff0c;一个与你一样正在慢慢前行的普通人。 &#x1f3c0;个人主页&#xff1a;陈童学哦CSDN &#x1f4a1;所属专栏&#xff1a;PTA &#x1f381;希望各…

首个跨云元数据KV存储Xline正式进入CNCF沙箱

2023年6月13日&#xff0c;云原生计算基金会&#xff08;CNCF&#xff09;宣布Xline正式被纳入CNCF沙箱(Sandbox&#xff09;项目。Xline是由达坦科技&#xff08;DatenLord&#xff09;于2022年年底推出的开源项目&#xff0c;是一个用Rust语言写就的&#xff0c;用于元数据管…

hello算法笔记之图

一、图的基础知识 图是一种非线性数据结构&#xff0c;由「顶点 Vertex」和「边 Edge」组成。 1.图的类型&#xff1a; 根据边是否具有方向可以分为有向图&#xff0c;无向图 根据所有顶点是否连通可以分为连通图&#xff08;对于连通图&#xff0c;从某个顶点出发&#xf…

gdb系列-入门篇-day01

gdb基础命令 一个程序要被调试&#xff0c;编译的时候要加上-g选项&#xff0c;例如gcc -g … 先准备一个调试的小代码 #include <stdio.h>int hello() {printf("hello\n");return 0; }int main() {int a[5] {1,2,3,4,5};hello();for(int i0; i<5; i){pri…

springboot使用@Valid 和 @Validated 注解校验详解以及编写一个自定义全局异常类

package com.test.springvalid.config;import lombok.Data; import java.util.HashMap; import java.util.Map;/*** 通用返回结果&#xff0c;服务端响应的数据最终都会封装成此对象* param <T>*/ Data public class R<T> {private Integer code; //编码&#xff1…

Mybatis源码分析_Mapper接口是如何实例化的 (2)

我们在使用Springmybatis的时候&#xff0c;经常都是直接写一个接口和一个对应的 ***Mapper.xml文件&#xff0c;然后业务代码就可以直接注入这个接口了。它是如何做到的呢&#xff1f; 接口&#xff1a; xml 想搞清楚这个问题&#xff0c;那还是要从Mybatis底层源码进行分析的…

智能小车使用IIC屏幕做动作显示界面

一、简介 使用0.96寸IIC屏幕作为遥控动作的显示界面。 外设引脚 stm32f103c8t6单片机IIC引脚有两组 使用I2C1&#xff0c;对应的时钟与数据线分别为PB6、PB7。 IIC屏幕指令 // OLED_WR_Byte(0xAE,OLED_CMD);//--turn off oled panel // OLED_WR_Byte(0x00,OLED_CMD);//---se…

07- c语言指针 (C语言)

一 指针的引入 1、一般把内存中的一个字节称为一个内存单元。 2、为了正确地访问这些内存单元&#xff0c;必须为每个内存单元编上号。根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址&#xff0c;通常也把这个地址称为指针。 3、如果在程序中定义…

车载网络测试 - CANCANFD - 基础篇_03

十、发送方式与过滤方式 1、广播发送及规则 我们以小组讨论现场为例来说明CAN总线广播发送规则&#xff1a; 1&#xff09;一个房间代表同一路CAN总线&#xff0c;每一个小组代表一个CAN Node&#xff0c;每一个小组成员发言代表发送一帧CAN报文&#xff0c;对所有的小组成员进…

生成对抗网络

1 GAN基本概念 1.1 GAN介绍 GAN的英文全称是Generative Adversarial Network&#xff0c;中文名是生成对抗网络。它由两个部分组成&#xff0c;生成器和鉴别器&#xff08;又称判别器&#xff09;&#xff0c;生成网络&#xff08;Generator&#xff09;负责生成模拟数据&…

【Python】异常处理 ④ ( 异常处理 else 语句 | 异常处理 finally 语句 )

文章目录 一、Python 异常捕获 else 语句1、异常捕获 else 语句2、代码示例 - 没有触发 else 语句的情况3、代码示例 - 触发 else 语句的情况 二、Python 异常捕获 finally 语句1、异常捕获 finally 语句2、代码示例 - 出现异常后执行 finally 语句 一、Python 异常捕获 else 语…

展示和标注图像:探索Gradio AnnotatedImage模块的功能

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…