C++设计模式 #8 抽象工厂(Abstract Factory)

抽象工厂这个名字比较难以帮助理解,可以把抽象工厂理解为"品牌工厂"或者"家族工厂"。

动机

  • 在软件系统中,经常面临着"一系列相互依赖的对象"的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。
  • 如何应对这种变化?如何绕过常规的创建方法(new),提供一种"封装机制"来避免客户程序和这种"多系列具体对象创建工作"的紧耦合?

举个栗子

我们有一个控制数据库连接和操作的类

cpp 复制代码
class EmployeeDAO{

public:
    vector<EmployeeDAO> GetEmployees(){
        SqlConnection* connection = new SqlConnection();
        connection->ConnectionString("...");

        SqlCommand* command = new SqlCommand();
        command->CommandText("...");
        command->SetConnection(connection);

        SqlDataReader* reader = command->ExecuteReader();
        while(reader->Read()){

        }
        
    }
};

我们使用这个类来连接一个数据库,并使用一些SQL语句来对数据库进行操作。然后使用reader来读取SQL语句得到的数据。

由于可以使用的数据库种类很多,比如针对oracle的数据库,我们可能需要另外再一个类来实现不同的实际操作。

在学习了工厂方法之后,我们可以根据工厂模式将SqlConnection,SqlCommand,SqlDataReader改写成以下模式。

cpp 复制代码
class IDBconnection {
public:
	virtual void ConnectionString(string) = 0;
};
class SqlConnection : public IDBconnection {
public:
	virtual void ConnectionString(string s) {
		//do connection
	}
};
class OracleConnection : public IDBconnection {
public:
	virtual void ConnectionString(string s) {
		//do connection
	}
};

class IDBconnectionFactory {
public:
	virtual IDBconnection* create() = 0;
};
class SqlConnectionFactory : public IDBconnectionFactory {
	IDBconnection* create() {
		return new SqlConnection();
	}
};
class OracleConnectionFactory : public IDBconnectionFactory {
	IDBconnection* create() {
		return new OracleConnection();
	}
};

class ISqlCommand {
public:
	virtual void CommandText(string) = 0;
	virtual void SetConnection(IDBconnection*) = 0;
	virtual IDataReader* ExecuteReader() = 0;
};
class SqlCommand : public ISqlCommand {
public:
	virtual void CommandText(string) {
		//...
	}
	virtual void SetConnection(IDBconnection*) {
		//...
	}
	virtual IDataReader* ExecuteReader() {
		return new SqlDataReader();
	}
};
class OracleCommand : public ISqlCommand {
public:
	virtual void CommandText(string) {
		//...
	}
	virtual void SetConnection(IDBconnection*) {
		//...
	}
	virtual IDataReader* ExecuteReader() {
		return new OracleDataReader();
	}
};

class ISqlCommandFactory {
public:
	virtual ISqlCommand* create() = 0;
};
class SqlCommandFactory : public ISqlCommandFactory {
	ISqlCommand* create() {
		return new SqlCommand();
	}
};
class OracleCommandFactory : public ISqlCommandFactory {
	ISqlCommand* create() {
		return new OracleCommand();
	}
};


class IDataReader {
public:
	virtual bool Read() = 0;
};
class SqlDataReader : public IDataReader {
public:
	virtual bool Read() {
		//...
		return true;
	}
};
class OracleDataReader : public IDataReader {
public:
	virtual bool Read() {
		//...
		return true;
	}
};


class EmployeeDAO {
private:
	IDBconnection* connection;
	ISqlCommand* command;
	IDataReader* reader;

public:

	vector<EmployeeDAO> GetEmployees(IDBconnectionFactory* connectionFactory, ISqlCommandFactory* commanFactory) {
		
		connection = connectionFactory->create();
		connection->ConnectionString("...");

		command = commanFactory->create();
		command->CommandText("...");
		command->SetConnection(connection);

		reader = command->ExecuteReader();
		while (reader->Read()) {

		}

	}
};

我们使用工厂模式在运行时创建EmployeeDAO的connection和command指针。

问题

我们使用单纯的工厂模式来创建不同的connection和command的子类来操作不同的数据库。这表面上看起来已经解决了这个问题。

但是,假如出现了这种情况,在调用GetEmployees时,我们传入的参数是,

cpp 复制代码
GetEmployees(SqlConnectionFactory, OracleCommandFactory)

这样的代码当然可以通过编译,但是我们用Oracle数据库的命令其他的数据库,当然会导致一系列的错误。

重构

cpp 复制代码
class IDBFactory {
public:
	virtual IDBconnection* create_connection() = 0;
	virtual ISqlCommand* create_command() = 0;
};

class SqlFactory : public IDBFactory {
public:
	virtual IDBconnection* create_connection() {
		return new SqlConnection();
	}
	virtual ISqlCommand* create_command() {
		return new SqlCommand();
	}
};

class OracleFactory : public IDBFactory {
public:
	virtual IDBconnection* create_connection() {
		return new OracleConnection();
	}
	virtual ISqlCommand* create_command() {
		return new OracleCommand();
	}
};


class EmployeeDAO {
private:
	IDBconnection* connection;
	ISqlCommand* command;
	IDataReader* reader;

public:

	vector<EmployeeDAO> GetEmployees(IDBFactory* factory) {
		
		connection = factory->create_connection();
		connection->ConnectionString("...");

		command = factory->create_command();
		command->CommandText("...");
		command->SetConnection(connection);

		reader = command->ExecuteReader();
		while (reader->Read()) {

		}

	}
};

将两个connection和command工厂合并,让它只能生产"同一种类的产品"。这样,我们既降低了耦合,又避免"产品种类"不同导致不能使用的情况。

结构

红色是稳定的,蓝色代表了一种变化,绿色代表了另一种变化。

可以对照上面的代码自己寻找一下图中的类和代码中的类的对应关系。

9.抽象工厂_哔哩哔哩_bilibili这里面,将ConreteFactory1,2其实是按sql和oracle分开的。视频中出现了一点小错误。

总结

  • 如果没有应对"多系列对象构建"的需求变化,则没有必要使用抽象工厂模式,这时候简单的工厂模式完全可以。
  • "系列对象"指在某一特定系列下的对象之间有相互依赖、或作用的关系。(不同数据库间的操作模块不能互相作用和依赖)
  • 抽象工厂模式主要应对"新系列"的需求变动。缺点在于难以应对"新对象"的需求变动。
相关推荐
Hhy_11071 小时前
《C++深度解构02》类和对象(上)——定义规范、内存对齐与 this 指针全解
c语言·开发语言·c++·学习·visual studio
晚风叙码1 小时前
C++哈希表实现:开放定址法和链地址法 (哈希桶)
数据结构·c++·哈希算法·散列表
呜喵王阿尔萨斯2 小时前
C/C++ 杂记-1
c++
不会代码的小猴2 小时前
2. 了解Qt
开发语言·c++·笔记·qt·算法
兵哥工控2 小时前
mfc实现在数值范围内实时改变静态文本控件字体颜色实例
c++·mfc
程序喵大人4 小时前
【C++进阶】STL算法与函数对象 - 09 函数对象保存状态并复用规则
开发语言·c++·算法·stl·函数对象
ShineWinsu10 小时前
对于C++:auto_ptr、unique_ptr、shared_ptr的模拟实现
c++·面试·笔试·智能指针·unique_ptr·shared_ptr·auto_ptr
用户9385156350712 小时前
工厂模式与 Nest.js 核心思想 —— 从蜜雪冰城到企业级架构
后端·设计模式·nestjs
XLYcmy12 小时前
京东 算法实习一面 下+手撕
c++·python·llm·概率论·数据处理·训练·codebert
反转180度13 小时前
Qt 6 + C++17 实现 SFTP 批量部署:工业设备运维工具实战解析
运维·c++·qt