Design pattern: abstract factory pattern C + + implementation

Posted by agent_smith_sp on Wed, 22 Dec 2021 06:50:29 +0100

preface

Today, I reviewed the abstract factory pattern. Before that, I reviewed the simple factory pattern and factory method pattern. Their functions are the same: to encapsulate the creation process of objects. If you are not familiar with simple factory mode and factory method mode, you can read the previous article:
Simple factory mode: https://blog.csdn.net/DU_YULIN/article/details/119778822
Factory method mode: https://blog.csdn.net/DU_YULIN/article/details/119821207

1. Understanding of abstract factory pattern

The abstract factory model, in my personal opinion, is an extension of the factory method model. In the image, the factory in the factory method model is the production of a single product, while the abstract factory model is the production of multiple products.
Here, taking the access of different databases (Sqlserver, Access) as an example, assuming that there is only User table in the database, the class diagram is implemented with factory method mode (referring to Dahua design mode) as follows:

Now that the Department table is added in the database, you need to implement the class diagram with the abstract factory pattern (refer to the big talk design pattern) as follows:

If you look at the implementation of the factory class in the abstract factory pattern and the factory method pattern, you will find that they differ only in the number of class methods: the factory class in the factory method pattern has only one method, while the factory class in the abstract factory pattern has two methods.

2. C + + implements the abstract factory pattern

The above database access example is implemented here:

#include <iostream>
#include <memory>

//DataBase Table - User
class User {
private:
	std::string id;
	std::string name;

public:
	User(): id(""), name("") {}
	User(const std::string& strId, const std::string& strName): id(strId), name(strName){}

	std::string GetId() const
	{
		return id;
	}

	std::string GetName() const
	{
		return name;
	}
};

//DataBase Table - Department
class Department
{
private:
	std::string id;
	std::string name;

public:
	Department(): id(""), name("") {}
	Department(const std::string& strId, const std::string& strName) : id(strId), name(strName) {}

	std::string GetId() const
	{
		return id;
	}

	std::string GetName() const
	{
		return name;
	}
};

// ****************abstract factory pattern classes***************

// DB_User classes which used to manage User info by different databases
class DB_User {
public:
	/*
	Note: different database has different method to manage data,
	so need abstract them into the abtract class,
	then concrete database can implement them.
	*/
	virtual void InsertUser(const std::shared_ptr<User> user) = 0;
	virtual const std::shared_ptr<User> GetUser(const std::string& strId) const = 0;
};

class Sqlserver_User: public DB_User {
public:
	virtual void InsertUser(const std::shared_ptr<User> user)
	{
		std::cout << "Inser user into Sql server Database!" << std::endl;
	}

	virtual const std::shared_ptr<User> GetUser(const std::string& strId) const
	{
		std::cout << "Search user from Sql server database by id!" << std::endl;
		return NULL;
	}
};

class Access_User : public DB_User {
public:
	virtual void InsertUser(const std::shared_ptr<User> user)
	{
		std::cout << "Inser user into access Database!" << std::endl;
	}

	virtual const std::shared_ptr<User> GetUser(const std::string& strId) const
	{
		std::cout << "Search user from access database by id!" << std::endl;
		return NULL;
	}
};

// DB_Department classes which used to manage Department info by different databases
class DB_Department
{
public:
	/*
	Note: different database has different method to manage data, 
	so need abstract them into the abtract class,
	then concrete database can implement them.
	*/
	virtual void InsertDepartment(const std::shared_ptr<Department> pDepartment) = 0;
	virtual std::shared_ptr<Department> GetDepartment(const std::string& strId) const = 0;
};

class Sqlserver_Department :  public DB_Department
{
public:
	virtual void InsertDepartment(const std::shared_ptr<Department> pDepartment)
	{
		std::cout << "Insert department info into sql server database!" << std::endl;
	}

	virtual std::shared_ptr<Department> GetDepartment(const std::string& strId) const
	{
		std::cout << "Search department from sql server database by id!" << std::endl;
		return NULL;
	}
};

class Access_Department : public DB_Department
{
public:
	
	virtual void InsertDepartment(const std::shared_ptr<Department> pDepartment)
	{
		std::cout << "Insert department info into access database!" << std::endl;
	}

	virtual std::shared_ptr<Department> GetDepartment(const std::string& strId) const
	{
		std::cout << "Search department from access database by id!" << std::endl;
		return NULL;
	}
};


// Factory classes which used to create DB_User and DB_Department objects
class Factory {
public:
	virtual std::shared_ptr<DB_User> CreateUserDBConnection() = 0;
	virtual std::shared_ptr<DB_Department> CreateDepartmentDBConnection() = 0;

	virtual ~Factory() {}
};

class SqlserverFactory : public Factory
{
public:
	virtual std::shared_ptr<DB_User> CreateUserDBConnection()
	{
		return std::move(std::make_shared<Sqlserver_User>());
	}

	virtual std::shared_ptr<DB_Department> CreateDepartmentDBConnection()
	{
		return std::move(std::make_shared<Sqlserver_Department>());
	}
};

class AccessFactory : public Factory
{
public:
	virtual std::shared_ptr<DB_User> CreateUserDBConnection()
	{
		return std::move(std::make_shared<Access_User>());
	}

	virtual std::shared_ptr<DB_Department> CreateDepartmentDBConnection()
	{
		return std::move(std::make_shared<Access_Department>());
	}
};

//****************Test*************

int main()
{
	std::shared_ptr<User> smartUser = std::make_shared<User>("1", "John");
	std::shared_ptr<Department> smartDepartment = std::make_shared<Department>("1", "Vision");

	// create database to sql server.
	std::shared_ptr<Factory> smartFactory = std::make_shared<SqlserverFactory>();

	std::shared_ptr<DB_User> smartUserDBConn = smartFactory->CreateUserDBConnection();
	smartUserDBConn->InsertUser(smartUser);
	smartUserDBConn->GetUser("1");

	std::shared_ptr<DB_Department> smartDepartmentDBConn = smartFactory->CreateDepartmentDBConnection();
	smartDepartmentDBConn->InsertDepartment(smartDepartment);
	smartDepartmentDBConn->GetDepartment("1");

	// change database to Access
	smartFactory = std::make_shared<AccessFactory>();

	smartUserDBConn = smartFactory->CreateUserDBConnection();
	smartUserDBConn->InsertUser(smartUser);
	smartUserDBConn->GetUser("1");

	smartDepartmentDBConn = smartFactory->CreateDepartmentDBConnection();
	smartDepartmentDBConn->InsertDepartment(smartDepartment);
	smartDepartmentDBConn->GetDepartment("1");


	system("pause");
	return 0;
}

summary

Advantages of the abstract factory method pattern:

  1. To facilitate switching between different implementations, such as the above database access example, if you want to switch the database access mode, you only need to modify the specific database factory:
std::shared_ptr<Factory> smartFactory = std::make_shared<SqlserverFactory>();

Replace with:

std::shared_ptr<Factory> smartFactory = std::make_shared<AccessFactory>();

The switching of database access mode is realized.

  1. The general advantage of the factory pattern is that it separates the object creation process from the client. The client manipulates the instance through the abstract interface, that is, the client only knows the DB_Department, DB_User, these abstract interface classes, I don't know Sqlserver_Department, Access_Department these specific database access classes.

Disadvantages of abstract factory pattern:

  1. If we add a new table project, we need to add at least three classes: DB_Project, Sqlserver_Project, Access_Project; At least three classes must be modified: Factory, SqlserverFactory and AccessFactory to encapsulate the new method of creating project objects. The modified class obviously violates the open closed principle, that is, it is closed for modification.
  2. When the create database access factory object is called at 100 in the code, if you switch the database access object again, you need to modify it at 100, which is inconsistent with our expectation that only one place will be modified, and all the calls will be changed automatically. (of course, this expectation can be achieved through reflection technology).

Topics: C++ Design Pattern