C++设计模式之工厂方法模式(Factory Method)

news2024/11/24 17:33:19

工厂方法模式(Factory Method) 头号公社

文章目录

    • 别名
    • 定义
    • 前言
      • 1、问题
      • 2、解决方案
    • 结构
    • 适用场景
    • 实现方式
    • 优点
    • 缺点
    • 与其他模式的关系
    • 实例
    • Reference

别名

虚拟构造函数(Virtual Constructor)。

定义

工厂方法是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。

前言

1、问题

假设你正在开发一款物流管理应用。最初版本只能处理卡车运输,因此大部分代码都在位于名为“卡车”的类中。

一段时间后,这款应用变得极受欢迎。你每天都能收到十几次来自海运公司的请求,希望应用能够支持海上物流功能。

《图片》

这可是个好消息。但是代码问题该如何处理呢?目前,大部分代码都与 卡车 类相关。在程序中添加 轮船 类需要修改全部代码。更糟糕的是,如果你以后需要在程序中支持另外一种运输方式,很可能需要再次对这些代码进行大幅修改。

最后,你将不得不编写繁复的代码,根据不同的运输对象类,在应用中进行不同的处理。

2、解决方案

工厂方法模式建议使用特殊的工厂方法代替对于对象构造函数的直接调用(即使用 new 运算符)。不用担心,对象仍将通过 new 运算符创建,只是该运算符改在工厂方法中调用罢了。工厂方法返回的对象通常被称作“产品”。

在这里插入图片描述

乍看之下,这种更改可能毫无意义:我们只是改变了程序中调用构造函数的位置而已。但是,仔细想一下,现在你可以在子类中重写工厂方法,从而改变其创建产品的类型。

但有一点需要注意:仅当这些产品具有共同的基类或者接口时,子类才能返回不同类型的产品,同时基类中的工厂方法还应将其返回类型声明为这一共有接口。

在这里插入图片描述

举例来说, 卡车(Truck)和 轮船(Ship)类都必须实现运输(Transport)接口, 该接口声明了一个交付(deliver)的方法。 每个类都将以不同的方式实现该方法:卡车走陆路交付货物,轮船走海路交付货物。

陆路运输(RoadLogistics)类中的工厂方法返回卡车对象,而海路运输(SeaLogistics)类则返回轮船对象。

在这里插入图片描述

调用工厂方法的代码(通常被称为客户端代码)无需了解不同子类返回实际对象之间的差别。客户端将所有产品视为抽象的“运输”。 客户端知道所有运输对象都提供“交付”方法,但是并不关心其具体实现方式。

结构

在这里插入图片描述

  1. 产品(Product)将会对接口进行声明。对于所有由创建者及其子类构建的对象,这些接口都是通用的。
  2. 具体产品(Concrete Products)是产品接口的不同实现。
  3. 创建者(Creator)类声明返回产品对象的工厂方法。该方法的返回对象类型必须与产品接口相匹配。 你可以将工厂方法声明为抽象方法,强制要求每个子类以不同方式实现该方法。或者,你也可以在基础工厂方法中返回默认产品类型。注意,尽管它的名字是创建者,但他最主要的职责并不是创建产品。一般来说,创建者类包含一些与产品相关的核心业务逻辑。工厂方法将这些逻辑处理从具体产品类中分离出来。打个比方,大型软件开发公司拥有程序员培训部门。但是,这些公司的主要工作还是编写代码,而非生产程序员。
  4. 具体创建者(Concrete Creators) 将会重写基础工厂方法,使其返回不同类型的产品。注意,并不一定每次调用工厂方法都会创建新的实例。工厂方法也可以返回缓存、对象池或其他来源的已有对象。

适用场景

● 当你在编写代码的过程中,如果无法预知对象确切类别及其依赖关系时,可使用工厂方法。

工厂方法将创建产品的代码与实际使用产品的代码分离,从而能在不影响其他代码的情况下扩展产品创建部分代码。例如,如果需要向应用中添加一种新产品,你只需要开发新的创建者子类,然后重写其工厂方法即可。

● 如果你希望用户能扩展你软件库或框架的内部组件,可使用工厂方法。

继承可能是扩展软件库或框架默认行为的最简单方法。但是当你使用子类替代标准组件时,框架如何辨识出该子类?解决方案是将各框架中构造组件的代码集中到单个工厂方法中,并在继承该组件之外允许任何人对该方法进行重写。

● 如果你希望复用现有对象来节省系统资源,而不是每次都重新创建对象,可使用工厂方法。

在处理大型资源密集型对象(比如数据库连接、文件系统和网络资源)时,你会经常碰到这种资源需求。

实现方式

  1. 让所有产品都遵循同一接口。该接口必须声明对所有产品都有意义的方法。
  2. 在创建类中添加一个空的工厂方法。该方法的返回类型必须遵循通用的产品接口。
  3. 在创建者代码中找到对于产品构造函数的所有引用。将它们依次替换为对于工厂方法的调用,同时将创建产品的代码移入工厂方法。你可能需要在工厂方法中添加临时参数来控制返回的产品类型。工厂方法的代码看上去可能非常糟糕。其中可能会有复杂的switch 分支 运算符,用于选择各种需要实例化的产品类。但是不要担心,我们很快就会修复这个问题。
  4. 现在,为工厂方法中的每种产品编写一个创建者子类,然后在子类中重写工厂方法,并将基本方法中的相关创建代码移动到工厂方法中。
  5. 如果应用中的产品类型太多,那么为每个产品创建子类并无太大必要,这时你也可以在子类中复用基类中的控制参数。
  6. 如果代码经过上述移动后,基础工厂方法中已经没有任何代码,你可以将其转变为抽象类。如果基础工厂方法中还有其他语句,你可以将其设置为该方法的默认行为。

优点

● 你可以避免创建者和具体产品之间的紧密耦合。

● 单一职责原则。你可以将产品创建代码放在程序的单一位置,从而使得代码更容易维护。

● 开闭原则。无需更改现有客户端代码,你就可以在程序中引入新的产品类型

缺点

应用工厂方法模式需要引入许多新的子类,代码可能会因此变得更复杂。最好的情况是将该模式引入创建者类的现有层次结构中。

与其他模式的关系

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

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

● 你可以同时使用工厂方法和迭代器来让子类集合返回不同类型的迭代器,并使得迭代器与集合相匹配。

● 原型并不基于继承,因此没有继承的缺点。另一方面,原型需要对被复制对象进行复杂的初始化。 工厂方法基于继承,但是它不需要初始化步骤。

● 工厂方法是模板方法的一种特殊形式。同时,工厂方法可以作为一个大型模板方法中的一个步骤。

实例

Creator.h:

#ifndef  CREATOR_H_
#define  CREATOR_H_

#include <memory>
#include "Product.h"

// 抽象工厂类 生产电影
class Factory {
 public:
    virtual std::shared_ptr<Movie> get_movie() = 0;
};

#endif  // CREATOR_H_

ConcreteCreator.h:

#ifndef CONCRETE_CREATOR_H_
#define CONCRETE_CREATOR_H_

#include <memory>
#include "Creator.h"
#include "ConcreteProduct.h"

// 具体工厂类 中国生产者
class ChineseProducer : public Factory {
 public:
    std::shared_ptr<Movie> get_movie() override { return std::make_shared<ChineseMovie>(); }
};

// 具体工厂类 日本生产者
class JapaneseProducer : public Factory {
 public:
    std::shared_ptr<Movie> get_movie() override { return std::make_shared<JapaneseMovie>(); }
};

// 具体工厂类 美国生产者
class AmericanProducer : public Factory {
 public:
    std::shared_ptr<Movie> get_movie() override { return std::make_shared<AmericanMovie>(); }
};

#endif  // CONCRETE_CREATOR_H_

Product.h:

#ifndef  PRODUCT_H_
#define  PRODUCT_H_

#include <string>

// 抽象产品类 电影
class Movie {
 public:
    virtual std::string get_a_movie() = 0;
};

#endif  // PRODUCT_H_

ConcreteProduct.h:

#ifndef  CONCRETE_PRODUCT_H_
#define  CONCRETE_PRODUCT_H_

#include <iostream>
#include <string>
#include "Product.h"

// 具体产品类 电影::国产电影
class ChineseMovie : public Movie {
 public:
    std::string get_a_movie() override {
        return "《让子弹飞》";
    }
};

// 具体产品类 电影::日本电影
class JapaneseMovie : public Movie {
 public:
    std::string get_a_movie() override {
        return "《千与千寻》";
    }
};

// 具体产品类 电影::美国电影
class AmericanMovie : public Movie {
 public:
    std::string get_a_movie() override {
        return "《钢铁侠》";
    }
};

#endif  // CONCRETE_PRODUCT_H_

main.cpp:

#include "ConcreteCreator.h"

int main() {
    std::shared_ptr<Factory> factory;
    std::shared_ptr<Movie> product;

    // 这里假设从配置中读到的是Chinese(运行时决定的)
    std::string conf = "China";

    // 程序根据当前配置或环境选择创建者的类型
    if (conf == "China") {
        factory = std::make_shared<ChineseProducer>();
    } else if (conf == "Japan") {
        factory = std::make_shared<JapaneseProducer>();
    } else if (conf == "America") {
        factory = std::make_shared<AmericanProducer>();
    } else {
        std::cout << "error conf" << std::endl;
    }

    product = factory->get_movie();
    std::cout << "获取一部电影: " << product->get_a_movie() << std::endl;
}

编译运行:

$g++ -g main.cpp -o factorymethod -std=c++11
$./factorymethod 
获取一部电影: 《让子弹飞》

 

Reference

[1] https://design-patterns.readthedocs.io/zh_CN/latest/creational_patterns/factory_method.html

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

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

相关文章

前端需要注意和了解的SEO

SEO的基本了解 1.什么是SEO? SEO&#xff08;Search Engine Optimization又叫做搜索引擎优化。是一种方式&#xff1a;利用搜索引擎的规则提高网站在有关搜索引擎内的自然排名。 2. 前端怎么理解SEO? 对于SEO引擎&#xff0c;在前端需要的是做出来的网站&#xff0c;页面…

从裸机启动开始运行一个C++程序(二)

先序文章请看&#xff1a; 从裸机启动开始运行一个C程序&#xff08;一&#xff09; 运行在8086上的第一个程序 既然硬件环境已经就绪了&#xff0c;那接下来&#xff0c;就要想办法让它运行我们的程序了。不过在此之前&#xff0c;我们必须要了解一下8086的主要架构&#xf…

小白安装 stabel diffusion 篇

windows 系统安装 stabel diffusion 软件的方法其实很简单&#xff0c;只有 4 步&#xff08;安装过程遇到的报错问题&#xff0c;本文有详细的解决方案&#xff0c;都是我踩过的坑&#xff0c;给大家探路了&#xff09;&#xff1a; 1、安装 python&#xff08;建议安装 3.10…

孙鑫VC++第五章 文本编程

目录 1. 插入符 1.1 创建文本插入符 1.2 创建图形插入符 2. 文字输出和OnDraw函数 2.1窗口重绘 2.2 添加字符串资源 3. 路径层和剪切区域 3.1 路径 3.2 裁剪区域 4. 字符输入 4.1 字符输入 5. 字幕变色功能的实现 5.1 设置字体 5.2字幕变色功能的实现 6. 总结 1…

windows11 安装WSL2全流程

文章目录 1、启用window子系统及虚拟化1.1 命令行方式1.2 使用图形界面 2、手动安装2.1、安装内核更新包2.2、设置默认WSL版本2.3、配置分发版本2.3.1 下载发行版本2.3.2 安装到C盘2.3.3 安装到D盘2.3.3.1 导出镜像2.3.3.2 导入镜像2.3.3.3 默认系统设置及多系统选择 3、自动安…

全景 I 0基础学习VR全景制作,第26章热点功能-文档

本期为大家带来蛙色VR平台&#xff0c;热点功能—文档功能操作。 功能位置示意 热点&#xff0c;指在全景作品中添加各种类型图标的按钮&#xff0c;引导用户通过按钮产生更多的交互&#xff0c;增加用户的多元化体验。 文档热点&#xff0c;即点击热点后会嵌入式弹出所选文档…

一次线上mysql 调优 ,join 的调优,索引优化(Block Nested Loop)

原因&#xff1a; 某接口调用十分缓慢&#xff0c;通过 Explain 发现是SQL问题 FROMorderInfo o LEFT JOIN orderDetail d ONo.orderCode d.orderCode LEFT JOIN user u ONo.userId u.userId LEFT JOIN product p ONd.productCode p.productCode LEFT JOIN adminUser au O…

Linux之打包压缩

1、参考 11-文件压缩与打包 linux tar压缩排除指定文件夹 2、打包与压缩 在windows上似乎打包和压缩是同一个东西&#xff0c;大家都明白你的意思&#xff0c;实际上是打包和压缩是两个过程&#xff0c;只不过常用zip压缩一站式解决了。 打包&#xff1a;就是将文件夹或多个…

《终身成长》笔记七——建设性的批评

目录 总结 经典摘录 我们能做什么 成长型思维模式与马上行动有异曲同工之妙 改变孩子的思维模式 两种思维模式对比 总结 《终身成长》是卡罗尔德韦克的代表作&#xff0c;在这部作品中&#xff0c;她以通俗易懂的笔触总结了自己对人类两种思维模式的研究。也许因为思维模…

mysq的约束学习

第13章_约束 1. 约束(constraint)概述 1.1 为什么需要约束 数据完整性&#xff08;Data Integrity&#xff09;是指数据的精确性&#xff08;Accuracy&#xff09;和可靠性&#xff08;Reliability&#xff09;。它是防止数据库中存在不符合语义规定的数据和防止因错误信息的…

鸿蒙Hi3861学习十五-Huawei LiteOS-M(Socket客户端)

一、简介 在网络编程的时候&#xff0c;不管是客户端还是服务端&#xff0c;都离不开Socket。那什么是Socket&#xff0c;这里做个简单介绍。详细的内容&#xff0c;可以参考这篇文章&#xff1a;WIFI学习一&#xff08;socket介绍&#xff09;_wifi socket_t_guest的博客-CSDN…

ChatGPT发展报告:原理、技术架构详解和产业未来(附下载)

今年12月1日&#xff0c;OpenAI推出人工智能聊天原型ChatGPT&#xff0c;再次赚足眼球&#xff0c;为AI界引发了类似AIGC让艺术家失业的大讨论。 据报道&#xff0c;ChatGPT在开放试用的短短几天&#xff0c;就吸引了超过 100 万互联网注册用户。并且社交网络流传出各种询问或…

其他类型的CMOS逻辑门

1.CMOS与非门 电路结构如图所示 如图所示&#xff0c;T1、 T3为两个串联的PMOS&#xff0c; T2、 T4为两个并联的NMOS. A、B有一个为“0”时&#xff0c;T2、 T4至少有一个截止&#xff0c; T1、 T3至少有一个导通&#xff0c;故输出为高电平&#xff0c;Y&#xff1d;1. A、…

用Colab免费部署AI绘画云平台Stable Diffusion webUI

Google Colab 版的 Stable Diffusion WebUI 1.4 webui github 地址&#xff1a;https://github.com/sd-webui/stable-diffusion-webui 平台搭建 今天就来交大家如果来搭建和使用这个云平台。 第一步: 打开链接 https://colab.research.google.com/github/altryne/sd-webu…

求最大字段和(穷举法、动态规划、分治法)

目录 1、案例要求2、算法设计与实现2.1 穷举法2.1.1 算法设计思路2.1.2 代码实现 2.2 动态规划2.2.1 算法设计思路2.2.2 实现代码 2.3 分治法2.3.1 算法实现思路2.3.2 代码实现 3、总结 1、案例要求 给定由n个整数&#xff08;可能为负整数&#xff09;组成的序列a1,a2,…,an&…

菜鸟对原型链的理解

1.什么是原型 函数下的prototype属性&#xff0c;是个指针&#xff0c;指向的对象就是原型 2.什么是原型链 很多个原型连接起来就是一条链了。 function Person() { } var test new Person(); 当我们new一个构造函数是&#xff0c;实例对象&#xff08;test&#xff09;&a…

Windows平台上的5种敏捷软件开发(过程)模型

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天总结一下Windows平台上的5种敏捷软件开发(过程)模型。 说到这个问题&#xff0c;你必须先知道除了敏捷模型还有没有其他什么模型&#xff1f;同时要比较模型的区别&#xff0c;首先还要看看什么叫软件开…

文件权限-chmod命令 – 改变文件或目录权限

Linux chmod命令 – 改变文件或目录权限 在Linux系统中&#xff0c;每个文件和目录都有自己的权限属性&#xff0c;这些属性包括读、写、执行等权限。通常情况下&#xff0c;只有文件的所有者和管理员可以设置文件权限&#xff0c;而普通用户只能管理自己文件的权限。为了更好…

数据结构总结5:堆

后续会有补充 堆 堆是一种数据结构&#xff0c;总是一棵完全二叉树&#xff0c;是使用数组存储的&#xff0c;是非线性的&#xff1b;并且要求树中所有的父亲都小于等于孩子&#xff08;小根堆&#xff09;/树中所有的父亲都大于等于孩子&#xff08;大根堆&#xff09;不一定…

人工智能基础部分16-神经网络与GPU加速训练的原理与应用

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能基础部分16-神经网络与GPU加速训练的原理与应用&#xff0c;在深度学习领域&#xff0c;神经网络已经成为了一种流行的、表现优秀的技术。然而&#xff0c;随着神经网络的规模越来越大&#xff0c;训练神经…