本文内容全部来源于B站,仅做个人学习使用:
【工厂模式?错!是工厂模式群!】
在此之前,笔者曾经发过两篇关于工厂模式的博客:
C++|设计模式(二)|简单工厂和工厂方法模式
C++|设计模式(三)|抽象工厂模式
可以先行回顾。
关于工厂模式(简单工厂模式、工厂方法模式和抽象工厂模式),其实本质上就是隐藏 new 的实现,让用户专注于业务的开发。
首先我们需要明确的是,工厂模式这四个字本身并不指代任何一种具体的设计模式,相应得,在没有上下文的情况下,他可能指代一切与对象创建相关的代码范式和设计模式。
比如不同类型的创建方法:抽象创建方法、创建方法...
名为「简单工厂的代码组织形式」
又或是「工厂方法」与「抽象工厂」这两种常见的面向对象设计模式。
这些概念直接是如何衔接和演化的呢?
接下来我们开始正式阐述工厂模式群的故事!
文章目录
- [创建方法 Creation Method 与 静态创建方法 Static Creation Method](#创建方法 Creation Method 与 静态创建方法 Static Creation Method)
- [简单工厂 Simple Factory](#简单工厂 Simple Factory)
- [工厂方法模式 Factory Method](#工厂方法模式 Factory Method)
- [抽象工厂模式 Abstract Factory](#抽象工厂模式 Abstract Factory)
- 总结
创建方法 Creation Method 与 静态创建方法 Static Creation Method
概念的演化必然伴随着需求或者代码结构的逐渐复杂,最开始名为创建方法的概念在一本书中被提及,实际上创建方法泛指所有可以创建对象的方法。
这个方法特制对构造函数的包装,通过这种方式我们可以在代码中使用语意更加明确的特定方法来进行对象创建,创建方法可以将对象的创建细节隐藏,并且更好得体现代码意图。从而增强代码的可维护性
案例如下:
在一个公司管理系统中,我们可以为 部门(Department) 创建具有特定职能的员工,而在创业初期,这个系统仅支持创建类型为 程序员(Programmer) 的员工。下面我们对其进行建模。
首先构建程序员对应的 Programmer 类,然后构建部门对应的 Department 类,在该类中我们创建一个 createEmployee() 的方法,该方法被调用后会返回一个新的 Programmer 类别对象,在主函数中我们创建 Department 类对象,并调用它的 create Employee 方法。
cpp
struct Programmer {};
struct Department {
auto createEmployee() {
return new Programmer();
}
};
int main(void) {
Department department {};
department.createEmployee();
//...
return 0;
}
其中的 createEmployee 方法就是我们的创建方法,他帮我们封装了 Programmer 类对象的创建细节,因为这个流程设置了与业务流程相关性更强的名字。
除了创建方法,我们还能看到名为 静态创建方法(Static Creation Method) 的概念,其实就是原有的作为类成员函数的创建方法修改为一个类静态方法。
cpp
struct Programmer {};
struct Department {
static auto createEmployee() {
return new Programmer();
}
};
int main() {
Department::createEmployee();
// ...
return 0;
}
相比于创建方法,静态创建方法为我们提供了一些代码上的额外灵活性,比如我们可以在静态创建方法中返回单例模式下的类全局对象,而不是每次都创建一个新对象 或者 当我们需要基于构造函数的不同参数值来区分不同的对象创建任务时我们可以将这些任务封装在不同的静态创建方法內。
简单工厂 Simple Factory
我们此时需要新增一个 Designer 类的员工,那么我们继续修改建模代码。通过使用简单工厂的代码组织方式,我们可以很快实现这个新需求。
其实它就是在创建方法的基础之上,让方法可以根据给定参数的不同来创建不同的对象。
cpp
#include <stdexcept>
enum class EmployeeType {
Programmer,
Designer,
};
struct Employee {
virtual ~Employee() {}
}
现在我们要利用多态,使我们的工厂函数 createEmployee 即可以返回 Programmer 对象,也能够返回 Designer 对象。
cpp
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 接口的具体实现,在各自的接口实现中他们会返回不同类型的员工对象。
cpp
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();
}
};
现在我们可以根据不同的子类工厂来创建不同类型的员工对象。子类工厂承担了具体对象的创建工作,而当需要支持新的员工类型时,我们只需要再为它添加新的子类工厂即可。
cpp
int main(void) {
ITDepartment itDepartment {};
itDepartment.createEmployee();
//...
return 0;
}
这就是工厂方法模式的基本形态,但是仍然没有体现出其核心价值。让我们来继续修改这段代码:
现在假设在公司管理系统中对于每一个新入职的员工,我们都需要为他创建对应的员工对象,并调用该对象上的 registerAccount 方法来注册新账户,这个方法在调用后会返回对应的账户ID,所以我们在所有员工对象的父类 Employee 中添加这个接口,然后由具体的员工对象类来实现这个接口。
cpp
#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 方法
cpp
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 的方法来实现这个固定的业务流程。
在这个方法中,虽然业务的执行步骤是固定的,但是每一个步骤所对应接口的具体实现是由那个子类来提供的则完全由调用方来决定,这便是工厂方法的核心,即:。
- 可以定义稳定的模版流程
- 流程中各步骤的具体内容由子类定义
cpp
ITDepartment itDepartment {};
itDepartment.onboard();
此时 onboard 内部 createEmployee 接口的实现便会由 ITDepartment 类来提供,而 registerAccount 接口的实现则会由 Programmer 类来提供。
这里是 UML 类图:
现在,公司管理系统迎来了重大更新,此时,系统不仅支持为部门创建不同类型的员工,还支持为部门创建不同类型的项目,比如,IT部门可以同时创建类型为程序员的员工或者创建类型为IT类型的项目,UI部门与此类似,那么我们应该如何修改代码来完成建模呢?接下来我们必须讨论一种名为抽象工厂的设计模式了!
抽象工厂模式 Abstract Factory
首先我们按照与 Employee 类相同的方式来创建所有不同项目对应的基类(父类)Project,在这个类中我们还定义了与项目相关的接口 assignTo ,这个接口可以将某个项目指派给对应账户ID的员工,还是相同的方式我们定义 ITProject 与 UIProject 这两个不同项目的子类,并提供 assignTo 接口的不同实现。
cpp
#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 接口的不同实现,到这里我们基本得到了组成抽象工厂模式的所有代码:
cpp
//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 类在内部需要的所有基础对象将会全部交由该抽象对象工厂对象来负责创建。
cpp
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 对象中
cpp
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 类中定义的业务流程方法,只是你需要告诉它(通过构造函数),这些业务流程方法具体是为哪个部门定义的 (哪个产品族对应的抽象工厂)。
cpp
int main(void) {
ITDepartment itDepartment {};
DepartmentManager iTDepartmentManager { itDepartment };
const auto project = itDepartmentManager.createProject();
const auto accountNo = itDepartmentManager.createEmployee();
project->assignTo(accountNo);
//...
return 0;
}
接下来展示相应的 UML 图: