C + + Design Pattern -- factory pattern

Posted by DanDaBeginner on Tue, 01 Feb 2022 00:21:24 +0100

Factory mode (factory)

Definition: a utility class that creates an instance of a derived class

Abstract Factory

Definition: a utility class that creates an instance of a derived class. You can also create an instance of a factory.

The factory pattern is useful when you need to create many different types of objects that derive from a common base class. The factory pattern defines the method of creating an object, and subclasses can override this method to specify the derived object created. Therefore, during the program running, the factory mode can pass in the description of the desired object (such as the string entered by the user), and then return a pointer to the desired object. When the base class interface is well designed, this pattern is very effective because there is no need to convert the returned object.

Problem

We want to determine the object created when the program runs according to some configuration parameters or application parameters. When we write code, we don't know what class to instantiate.

Solution

Define the interface to create the object, and then let the subclass decide what class to instantiate. The factory pattern allows classes to defer instantiation to subclasses.

In the following example, factory mode is used to create a laptop or desktop computer at run time.
First, define an abstract base class (Interface) Computer and derived classes: Laptop and Desktop.

 class Computer
 {
 public:
     virtual void Run() = 0;
     virtual void Stop() = 0;
     
     virtual ~Computer() {}; /* without this, you do not call Laptop or Desktop destructor in this example! */
 };
 class Laptop: public Computer
 {
 public:
     void Run() override {mHibernating = false;}; 
     void Stop() override {mHibernating = true;}; 
     virtual ~Laptop() {}; /* because we have virtual functions, we need virtual destructor */
 private:
     bool mHibernating; // Whether or not the machine is hibernating
 };
 class Desktop: public Computer
 {
 public:
     void Run() override {mOn = true;}; 
     void Stop() override {mOn = false;}; 
     virtual ~Desktop() {};
 private:
     bool mOn; // Whether or not the machine has been turned on
 };

The ComputerFactory class returns the pointer of the Computer class according to the parameter description

 class ComputerFactory
 {
 public:
     static Computer *NewComputer(const std::string &description)
     {
         if(description == "laptop")
             return new Laptop;
         if(description == "desktop")
             return new Desktop;
         return nullptr;
     }
 };

Let's analyze the benefits of this design pattern. First, the benefits of compilation. If we put the Computer interface class and ComputerFactory factory class in a separate header file, and put the newcomputer () function and derived classes in a separate implementation file. This file containing the NewComputer() function is the only one that relies on derived classes. Therefore, if the derived class of Computer is changed or a new derived class is added, the file containing the NewComputer() function is the only file that needs to be recompiled. Anyone who uses this factory pattern only needs to ensure the consistency of this interface in the whole software.
Then, if you need to add a derived class, the original code that calls the factory pattern to generate the object does not need to be changed. To generate a new derived class object, simply pass a new string to the factory, and the factory will return a new derived class object.

Another example (C++17 standard):

#include <stdexcept>
#include <iostream>
#include <memory>
using namespace std;

class Pizza {
public:
	virtual int getPrice() const = 0;
	virtual ~Pizza() {};  /* without this, no destructor for derived Pizza's will be called. */
};

class HamAndMushroomPizza : public Pizza {
public:
	virtual int getPrice() const { return 850; };
	virtual ~HamAndMushroomPizza() {};
};

class DeluxePizza : public Pizza {
public:
	virtual int getPrice() const { return 1050; };
	virtual ~DeluxePizza() {};
};

class HawaiianPizza : public Pizza {
public:
	virtual int getPrice() const { return 1150; };
	virtual ~HawaiianPizza() {};
};

class PizzaFactory {
public:
	enum PizzaType {
		HamMushroom,
		Deluxe,
		Hawaiian
	};

	static unique_ptr<Pizza> createPizza(PizzaType pizzaType) {
		switch (pizzaType) {
		case HamMushroom: return make_unique<HamAndMushroomPizza>();
		case Deluxe:      return make_unique<DeluxePizza>();
		case Hawaiian:    return make_unique<HawaiianPizza>();
		}
		throw "invalid pizza type.";
	}
};

/*
* Create all available pizzas and print their prices
*/
void pizza_information(PizzaFactory::PizzaType pizzatype)
{
	unique_ptr<Pizza> pizza = PizzaFactory::createPizza(pizzatype);
	cout << "Price of " << pizzatype << " is " << pizza->getPrice() << std::endl;
}

int main()
{
	pizza_information(PizzaFactory::HamMushroom);
	pizza_information(PizzaFactory::Deluxe);
	pizza_information(PizzaFactory::Hawaiian);
}

Topics: C++ Design Pattern