C++|设计模式(八)|⭐️工厂模式?错!是工厂模式群!

news2024/9/22 7:21:31

本文内容全部来源于B站,仅做个人学习使用:
【工厂模式?错!是工厂模式群!】

在此之前,笔者曾经发过两篇关于工厂模式的博客:
C++|设计模式(二)|简单工厂和工厂方法模式
C++|设计模式(三)|抽象工厂模式
可以先行回顾。

关于工厂模式(简单工厂模式、工厂方法模式和抽象工厂模式),其实本质上就是隐藏 new 的实现,让用户专注于业务的开发。

首先我们需要明确的是,工厂模式这四个字本身并不指代任何一种具体的设计模式,相应得,在没有上下文的情况下,他可能指代一切与对象创建相关的代码范式和设计模式。

比如不同类型的创建方法:抽象创建方法、创建方法…
名为「简单工厂的代码组织形式」
又或是「工厂方法」与「抽象工厂」这两种常见的面向对象设计模式。
这些概念直接是如何衔接和演化的呢?
接下来我们开始正式阐述工厂模式群的故事

文章目录

  • 创建方法 Creation Method 与 静态创建方法 Static Creation Method
  • 简单工厂 Simple Factory
  • 工厂方法模式 Factory Method
  • 抽象工厂模式 Abstract Factory
  • 总结

创建方法 Creation Method 与 静态创建方法 Static Creation Method

概念的演化必然伴随着需求或者代码结构的逐渐复杂,最开始名为创建方法的概念在一本书中被提及,实际上创建方法泛指所有可以创建对象的方法。

这个方法特制对构造函数的包装,通过这种方式我们可以在代码中使用语意更加明确的特定方法来进行对象创建,创建方法可以将对象的创建细节隐藏,并且更好得体现代码意图。从而增强代码的可维护性

案例如下:
在一个公司管理系统中,我们可以为 部门(Department) 创建具有特定职能的员工,而在创业初期,这个系统仅支持创建类型为 程序员(Programmer) 的员工。下面我们对其进行建模。

首先构建程序员对应的 Programmer 类,然后构建部门对应的 Department 类,在该类中我们创建一个 createEmployee() 的方法,该方法被调用后会返回一个新的 Programmer 类别对象,在主函数中我们创建 Department 类对象,并调用它的 create Employee 方法。

struct Programmer {};
struct Department {
	auto createEmployee() {
		return new Programmer();
	}
};
int main(void) {
	Department department {};
	department.createEmployee();
	//...
	return 0;
} 

其中的 createEmployee 方法就是我们的创建方法,他帮我们封装了 Programmer 类对象的创建细节,因为这个流程设置了与业务流程相关性更强的名字。

除了创建方法,我们还能看到名为 静态创建方法(Static Creation Method) 的概念,其实就是原有的作为类成员函数的创建方法修改为一个类静态方法。

struct Programmer {};
struct Department {
	static auto createEmployee() {
		return new Programmer();
	}
};
int main() {
	Department::createEmployee();
	// ...
	return 0;
}

相比于创建方法,静态创建方法为我们提供了一些代码上的额外灵活性,比如我们可以在静态创建方法中返回单例模式下的类全局对象,而不是每次都创建一个新对象 或者 当我们需要基于构造函数的不同参数值来区分不同的对象创建任务时我们可以将这些任务封装在不同的静态创建方法內。

简单工厂 Simple Factory

我们此时需要新增一个 Designer 类的员工,那么我们继续修改建模代码。通过使用简单工厂的代码组织方式,我们可以很快实现这个新需求。

其实它就是在创建方法的基础之上,让方法可以根据给定参数的不同来创建不同的对象。

#include <stdexcept>

enum class EmployeeType {
	Programmer,
	Designer,
};
struct Employee {
	virtual ~Employee() {}
}

现在我们要利用多态,使我们的工厂函数 createEmployee 即可以返回 Programmer 对象,也能够返回 Designer 对象

struct Programmer : public Employee {};
struct Designer : public Employee {}; 
struct Department {
	Employee* createEmployee(EmployeeType type) {
		swith (type) {
			case EmployeeType::Programmer:
				return new Programmer();
			case EmployeeType::Designer:
				return new Designer();
			default:
				throw std::invalid_argument("Unknown type.");
		}
	}
};
int main(void) {
	Department department {};
	department.createEmployee(EmployeeType::Designer);
	//...
	return 0;
}

简单工厂非常直观,如果我们后续要增加新的员工类型,我们不得不再次修改 createEmployee 方法的实现细节,这违反了“开闭原则”。

那么答案就是 工厂方法模式!接下来我们将其一步步修改为工厂方法模式。

工厂方法模式 Factory Method

工厂方法模式的一个重要思想就是将对象的创建过程下放到不同的子类工厂,你可以理解为我们不仅在仅有的 Department 类中来创建这些不同类型的对象,相应得我们会让名为 ITDepartment 的类来负责创建 Programmer 类对象,而让 UIDepartment 类来负责创建 Designer 类对象,而 Department 类将作为他们的父类定义这两个子类工厂应该实现哪些接口,createEmployee 就是其中之一。

所以我们需要将 createEmployee 从一个成员方法变为一个抽象接口。并且为 Department 类添加相应的虚析构函数,然后按照刚刚讲解的流程创建 ITDepartment 和 UIDepartment 这两个子类工厂,他们都继承自 Department 类并提供 createEmployee 接口的具体实现,在各自的接口实现中他们会返回不同类型的员工对象。

struct Employee {
	virtual ~Employee() {}
};

struct Programmer : public Employee {};
struct Designer : public Employee {};
struct Department {
	virtual ~Department() {}
	virtual Employee* createEmployee() = 0;
};

struct ITDepartment : public Department {
	Employee* createEmployee() const override {
		return new Programmer();
	}
};
struct UIDepartment : public Department {
	Employee* createEmployee() const override {
		return new Designer();
	}
};

现在我们可以根据不同的子类工厂来创建不同类型的员工对象。子类工厂承担了具体对象的创建工作,而当需要支持新的员工类型时,我们只需要再为它添加新的子类工厂即可。

int main(void) {
	ITDepartment itDepartment {};
	itDepartment.createEmployee();
	//...
	return 0;
}

这就是工厂方法模式的基本形态,但是仍然没有体现出其核心价值。让我们来继续修改这段代码:

现在假设在公司管理系统中对于每一个新入职的员工,我们都需要为他创建对应的员工对象,并调用该对象上的 registerAccount 方法来注册新账户,这个方法在调用后会返回对应的账户ID,所以我们在所有员工对象的父类 Employee 中添加这个接口,然后由具体的员工对象类来实现这个接口。

#include <iostream>
#include <cstdlib>

struct Employee {
	virtual ~Employee() {}
	virtual int registerAccount() const = 0;
};

struct Programmer : public Employee {
	int registerAccount() const override {
		const auto accountNo = rand();
		std::cout << "A programmer account: " << accountNo << ".\n";
		return accountNo;
	}
};

struct Designer : public Employee {
	int registerAccount() const override {
		const auto accountNo = rand();
		std::cout << "A programmer account: " << accountNo << ".\n";
		return accountNo;
	}
};

我们可以发现对于每一个新员工,无论是 Programmer 还是 Designer ,他都需要特定的两个步骤来完成公司管理系统规定的入职流程:

  • 创建员工对象
  • 调用该对象上的 registerAccount 方法
struct Department {
	virtual ~Department() {}
	virtual Employee* createEmployee() = 0;
	void onboard() const {
		Employee* employee = this->createEmployee();
		employee->registerAccount();
		delete employee;
	}
};

struct ITDepartment : public Department {
	Employee* createEmployee() const override {
		return new Programmer();
	}
};
struct UIDepartment : public Department {
	Employee* createEmployee() const override {
		return new Designer();
	}
};

int main(void) {
	ITDepartment itDepartment {};
	itDepartment.onboard();
	//...
	return 0;
}

这两个步骤对于所有员工来说都是统一的,因此我们说他们构成了一个固定的业务流程模板,所以接下来在 Department 类中我们新增了一个名为 onboard 的方法来实现这个固定的业务流程。

在这个方法中,虽然业务的执行步骤是固定的,但是每一个步骤所对应接口的具体实现是由那个子类来提供的则完全由调用方来决定,这便是工厂方法的核心,即:

  • 可以定义稳定的模版流程
  • 流程中各步骤的具体内容由子类定义
	ITDepartment itDepartment {};
	itDepartment.onboard();

此时 onboard 内部 createEmployee 接口的实现便会由 ITDepartment 类来提供,而 registerAccount 接口的实现则会由 Programmer 类来提供。

这里是 UML 类图:

现在,公司管理系统迎来了重大更新,此时,系统不仅支持为部门创建不同类型的员工,还支持为部门创建不同类型的项目,比如,IT部门可以同时创建类型为程序员的员工或者创建类型为IT类型的项目,UI部门与此类似,那么我们应该如何修改代码来完成建模呢?接下来我们必须讨论一种名为抽象工厂的设计模式了!

抽象工厂模式 Abstract Factory

首先我们按照与 Employee 类相同的方式来创建所有不同项目对应的基类(父类)Project,在这个类中我们还定义了与项目相关的接口 assignTo ,这个接口可以将某个项目指派给对应账户ID的员工,还是相同的方式我们定义 ITProject 与 UIProject 这两个不同项目的子类,并提供 assignTo 接口的不同实现。

#include <iostream>
#include <vector>
#include <unordered_map>

struct Employee {
	...
};

struct Project {
	virtual ~Project() {}
	virtual void assignTo(int) const = 0; 
};

struct Programmer : public Employee {
	...
};
struct Designer : public Employee {
	...
};

struct ITProject : public Project {
	void assignTo(int accountNo) const override {
		std::cout << "Assign an IT project to " << accountNo << ".\n";
	}
};
struct UIProject : public Project {
	void assignTo(int accountNo) const override {
		std::cout << "Assign an UI project to " << accountNo << ".\n";
	}
};

随后在 Department 类中我们删除了之前的 onboard 方法,并提添加了与 createEmployee 类似的 createProject 接口,接着在 ITDepartment 和 UIDepartment 这两个子类工厂实现中我们提供针对 createProject 接口的不同实现,到这里我们基本得到了组成抽象工厂模式的所有代码:

//struct Department {
//	virtual ~Department() {}
//	virtual Employee* createEmployee() = 0;
//	void onboard() const {
//		Employee* employee = this->createEmployee();
//		employee->registerAccount();
//		delete employee;
//	}
//};

struct Department {
	virtual ~Department() {}
	virtual Employee* createEmployee() = 0;
	void onboard() const {
		Employee* employee = this->createEmployee();
		employee->registerAccount();
		delete employee;
	}
};

struct ITDepartment : public Department {
	Employee* createEmployee() const override {
		return new Programmer();
	}
	Project* createProject() const override {
		return new ITProject();
	}
};
struct UIDepartment : public Department {
	Employee* createEmployee() const override {
		return new Designer();
	}
	Project* createProject() const override {
		return new UIProject();
	}
};

实际上,它与工厂方法模式的区别抛开代码结构上的相似,工厂方法模式的核心价值更多体现在流程模版的抽象上(也就是我们之前介绍的 onboard 方法),而抽象工厂模式则通常被视作一种对象生成器来使用,并且,它更多的用来生成属于同一产品族但不同系列的产品。

有了这些概念之后,我们继续修改代码,尝试从使用方的角度来观察抽象工厂模式的一些特征:

我们创建一个 DepartmentManager 类,这个类会在内部提供与业务流程相关的一系列函数封装,比如创建项目对应的 createProject 以及创建员工对应的 createEmployee ,这些函数的定位与我们之前介绍的 工厂方法模式下的 onboard 函数类似,DepartmentManager 类对象在构造时接受一个抽象工厂对象,抽象工厂作为对象生成器,DepartmentManager 类在内部需要的所有基础对象将会全部交由该抽象对象工厂对象来负责创建。

struct DepartmentManager {
	const Department& department;
	std::vector<Project*> projects;
	std::unordered_map<int, Employee*> employees;
	explicit DepartmentManager(const Department& department)
		: department(department) {}
};

比如我们先来看 createProject 方法的实现细节,这个方法通过抽象工厂对象的 createProject 方法来生成项目对象,随后该对象被保存在全局的 projects 向量中并返回给调用方;

类似得,createEmployee 方法也会通过抽象工厂对象的 createEmployee 方法来生成员工对象,这些员工对象随后将以账户ID作为Key,被存放在 employees 这个全局的 Map 对象中

struct DepartmentManager {
	...
	auto createProject() {
		const auto project = department.createProject();
		projects.push_back(project);
		return project;
	}
	int createEmployee() {
		const anto employee = department.createEmployee();
		const anto accountNo = employee->registerAccount();
		employees[accountNo] = employee;
		return accountNo;
	}
	...
};

最后在 main 函数中我们可以方便地使用 DepartmentManager 类中定义的业务流程方法,只是你需要告诉它(通过构造函数),这些业务流程方法具体是为哪个部门定义的 (哪个产品族对应的抽象工厂)。

int main(void) {
	ITDepartment itDepartment {};
	DepartmentManager iTDepartmentManager { itDepartment };
	const auto project = itDepartmentManager.createProject();
	const auto accountNo = itDepartmentManager.createEmployee();
	project->assignTo(accountNo);
	//...
	return 0;
}

接下来展示相应的 UML 图:

总结

在这里插入图片描述

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

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

相关文章

软件测试---Jmeter

一、简介 二、安装与启动 &#xff08;1&#xff09;安装 安装包&#xff1a;通过百度网盘分享的文件&#xff1a;jmeter环境.rar 链接&#xff1a;https://pan.baidu.com/s/1OB0IP3W7hqUjAGj_5F56sQ

vue3 自定义指令 自动获取节点的width 和 height

想写一个依赖库, 但是需要监听组件的width和height这些数据, 就找到了ResizeObserver这个方法,不想每次使用的时候都要创建和销毁 ResizeObserver, 索性就直接封装成为一个指令用来获取想要的信息, ResizeObserver对象上能够获取的信息还是非常多的, 除了width, height 还有 to…

一篇文章讲明白Ldraw(乐高模型)的格式文件说明

最好将文章内容保存下来 https://ldraw.org/article/218.html 乐高模型是非常有意思的模型&#xff0c;弄明白了它的模型构造&#xff0c;也就懂了三维模型的构造&#xff0c;原理都是相通的。

如何在行空板上运行 YOLOv10n?

YOLOv10介绍 YOLO&#xff08;You Only Look Once&#xff09;系列是当前最主流的端侧目标检测算法&#xff0c;由Joseph Redmon等人首次提出&#xff0c;并随着时间发展&#xff0c;已经推出了多个版本&#xff0c;每个版本“似乎”都在性能和速度上有所提升。 本文为大家介绍…

【前端编程小白】的HTML从零入门到实战

之前有高中毕业生读了博客&#xff0c;想让我帮他找一些前端入门的内容&#xff0c;他们报的计算机专业&#xff0c;想利用开学前夕学习一下&#xff0c;我给他推荐了一些菜鸟教程呀什么的。后来想&#xff0c;看来还是很多人需要一些更加入门的可成的&#xff0c;而且很多教程…

24年电赛——自动行驶小车(H题)基于 CCS Theia -陀螺仪 JY60 代码移植到 MSPM0G3507(附代码)

前言 只要搞懂 M0 的代码结构和 CCS 的图形化配置方法&#xff0c;代码移植就会变的很简单。因为本次电赛的需要&#xff0c;正好陀螺仪部分代码的移植是我完成的。&#xff08;末尾附全部代码&#xff09; 一、JY60 陀螺仪 JY60特点 1.模块集成高精度的陀螺仪、加速度计&…

APACHE安装与应用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

24澳中博览会|2025非洲水协年会暨展览|2025山西水展

2024澳中博览会 2025非洲水协年会暨展览 2025年山西国际水展暨水利工程设计与施工、水处理技术设备、泵管阀、智慧水务及环保展 承办单位&#xff1a;山西泽嘉国际展览有限公司 上海泽嘉展览服务有限公司 战略合作伙伴 &#xff1a; 美国迈阿密水展 欧 洲 海 水 脱 盐 淡 化…

SD原班人马发布FLUX.1:打开AI绘画新世界

​ Black Forest Labs 旗下产品 AI 绘画工具如雨后春笋般涌现&#xff0c;让我们对创作的理解不断刷新。就在大家以为已经见识了 AI 绘画的天花板时&#xff0c;FLUX.1 出现了&#xff01;这款由 Black Forest Labs 推出的 AI 绘画工具&#xff0c;不仅在性能上远超竞品&#x…

不好用你打我!2024你必须要会的AI神器

这篇文章&#xff0c;除了干货就是干货~ 今天给大家介绍一款2024年你必须要掌握的AI神器。 我可以肯定的说他是目前市面上第一款在这个领域出现的AI工具。 现在的AI工具&#xff0c;可以用来生成文字、图片、视频甚至音乐&#xff0c; 但是你听说过直接用AI生成APP的吗&…

【Spring】Spring框架的概念,以及Spring框架的简单使用。

目录 1. 概念 2. Spring的体系结构介绍&#xff08;了解&#xff09; 3. Spring框架的使用 3.1 环境准备 3.2 代码编写 1. 概念 总的来说就是一句话&#xff0c;Spring框架是一个轻量级的控制反转&#xff08;IoC&#xff09;和面向切面&#xff08;AOP&#xff09;编程的容…

Spring Boot+MyBatis+MySQL如何实现读写分离

​ 博客主页: 南来_北往 系列专栏&#xff1a;Spring Boot实战 背景 读写分离是数据库架构中的一种优化策略&#xff0c;它将读操作&#xff08;查询&#xff09;和写操作&#xff08;更新、插入、删除&#xff09;分开处理&#xff0c;通常通过将读请求和写请求分别发送…

2024年全国青少年信息素养大赛总决赛日赛程表

2024全国青少年信息素养大赛赛程表分赛场&#xff08;浙江传媒学院桐乡校区、桐乡技师学院&#xff09;日期地点时间赛项16日传媒学院8:00-9:00检录 9:00-10:30开赛图形化编程挑战赛&#xff08;小学1-3年级&#xff09;A组12:00-13:00检录 13:00-14:30开赛图形化编程挑战赛&am…

最新版Baby Audio Bundle,win和mac,持续更新,长期有效

一。Baby Audio Bundle.2024.07.WiN&MAC Baby Audio让您的混音听起来比以往任何时候都更大&#xff0c;更好&#xff0c;更有活力。这个捆绑包有七个独特的插件&#xff0c;涵盖了从延迟和混响效果&#xff08;Spaced Out&#xff09;到低保真声音&#xff08;Super VHS&am…

MySQL(8.0)数据库安装和初始化以及管理

1.MySQL下载安装和初始化 1.下载安装包 下载地址&#xff1a;https://downloads.mysql.com/archives/get/p/23/file/mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar wget https://downloads.mysql.com/archives/get/p/23/file/mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar 2.解压…

手把手使用 SVG + CSS 实现渐变进度环效果

效果 轨道 使用 svg 画个轨道 <svg viewBox"0 0 100 100"><circle cx"50" cy"50" r"40" fill"none" stroke-width"10" stroke"#333"></circle></svg>简单的说&#xff0c;就是…

shell脚本(自动化安装各种服务)

1、自动化配置DNS服务 [rootelemestatic ~]# vim dns.sh [rootelemestatic ~]# bash dns.sh 客户端测试&#xff1a; yum -y install bind-utils echo "nameserevr 192.168.8.161" > /etc/resolv.conf nslookup www.a.com 2、自动化配置rsync服务 [rootele…

如何用Python删除电脑中的重复文件?

在生活中&#xff0c;我们经常会遇到电脑中文件重复的情况。 在文件较少的情况下&#xff0c;这类情况还比较容易处理&#xff0c;最不济就是一个个手动对比删除&#xff1b; 而在重复文件很多的时候&#xff0c;我们很难保证把重复文件全部删完。 这里给大家带来了一个便捷…

《计算机组成原理》(第3版)第2章 计算机的发展及应用 复习笔记

第2章 计算机的发展及应用 一、计算机的产生和发展 &#xff08;一&#xff09;第一代电子管计算机 1943年&#xff0c;美国国防部批准了建造一台用电子管组成的电子数字积分机和计算机&#xff08;Electronic Numerica1 Integrator And Computer&#xff0c;ENIAC&#xff…

2024年06月 Scratch 图形化(四级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch图形化等级考试(1~4级)全部真题・点这里 一、单选题(共10题,共30分) 第1题 运行下列程序,输入单词“PLAY”,最后角色说?( ) A:LY4AP B:AP4LY C:YA4PL D:PL4AY 答案:B 根据程序分析可知,首先获取单词字符数,然后奇数位的字母放在字符数左侧,偶数位…