Abstract Factory Design Pattern in C++
The abstract factory design pattern is a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes.
The abstract factory pattern is useful when a system should be independent of how its products are created, composed, and represented. It provides a way to encapsulate a group of individual factories with a common theme without specifying their concrete classes.
To implement the abstract factory pattern, you need to:
- Declare the abstract factory interface with a set of methods for creating each product.
- Declare the abstract product interfaces with a set of methods for each product.
- Declare concrete product classes that implement abstract product interfaces.
- Declare concrete factory classes that implement the abstract factory interface and create concrete products.
Example
Here is an example of how the abstract factory pattern could be implemented in C++:
#include <iostream>
#include <memory>
// Declare the abstract factory interface
class AbstractFactory {
public:
virtual std::unique_ptr<AbstractProductA>CreateProductA() = 0;
virtual std::unique_ptr<AbstractProductB>CreateProductB() = 0;
};
// Declare the abstract product interfaces
class AbstractProductA {
public:
virtual void OperationA() = 0;
};
class AbstractProductB {
public:
virtual void OperationB() = 0;
};
// Declare the concrete products
class ConcreteProductA1 : public AbstractProductA {
public:
void OperationA() override {
std::cout<< "ConcreteProductA1::OperationA()\n";
}
};
class ConcreteProductB1 : public AbstractProductB {
public:
void OperationB() override {
std::cout<< "ConcreteProductB1::OperationB()\n"; }
};
class ConcreteProductA2 : public AbstractProductA {
public:
void OperationA() override {
std::cout<< "ConcreteProductA2::OperationA()\n"; }
};
class ConcreteProductB2 : public AbstractProductB {
public:
void OperationB() override {
std::cout<< "ConcreteProductB2::OperationB()\n";
}
};
// Declare a concrete factory
class ConcreteFactory1 : public AbstractFactory {
public:
std::unique_ptr<AbstractProductA>CreateProductA() override {
return std::make_unique<ConcreteProductA1>();
}
std::unique_ptr<AbstractProductB>CreateProductB() override {
return std::make_unique<ConcreteProductB1>();
}
};
class ConcreteFactory2 : public AbstractFactory {
public:
std::unique_ptr<AbstractProductA>CreateProductA() override {
return std::make_unique<ConcreteProductA2>();
}
std::unique_ptr<AbstractProductB>CreateProductB() override {
return std::make_unique<ConcreteProductB2>();
}
};
int main() {
std::unique_ptr<AbstractFactory> factory1 = std::make_unique<ConcreteFactory1>();
auto productA1 = factory1->CreateProductA();
auto productB1 = factory1->CreateProductB();
productA1->OperationA();
productB1->OperationB();
std::unique_ptr<AbstractFactory> factory2 = std::make_unique<ConcreteFactory2>();
auto productA2 = factory2->CreateProductA();
auto productB2 = factory2->CreateProductB();
productA2->OperationA();
productB2->OperationB();
return 0;
}
This example defines an interface for creating products and an interface for creating factories.
Advantages:
There are several advantages to using the Abstract Factory design pattern:
- It provides a way to create families of related objects without specifying their concrete classes. This allows you to create objects suited to a particular content without knowing exactly what those objects are.
- It promotes consistency among objects. When using the Abstract Factory pattern, all objects created by the factory will have a consistent interface, which helps to ensure that they can be used interchangeably.
- It allows you to change the objects created by the factory independently of the code that uses them. This can be useful if you need to change the behavior of the objects or if you need to support multiple versions of the objects.
- It can simplify the code that uses the objects since it doesn't need to know about the concrete classes of the objects it is using. This makes it easier to maintain and extend the code.
- It can make it easier to test the code that uses the objects since you can use mock objects in place of the real objects created by the factory.
Disadvantage:
- One potential disadvantage of the Abstract Factory pattern is that it can make adding new types of objects to the system more difficult.
- Since the factory creates objects based on an interface, you will need to create a new concrete implementation of the interface for each new type of object you want to support. This can increase the complexity of the system and make it more difficult to understand and maintain.
- Another potential disadvantage is that it can make the code more difficult to read and understand since it introduces an additional level of abstraction. This can make it harder for developers to understand how the system is intended to work and may increase the time required to make changes to the system.
- Finally, the Abstract Factory pattern may not be the most appropriate solution in all cases. It is most useful when you need to create families of related objects and when you need to be able to change the objects created by the factory independently of the code that uses them. If your system does not have these requirements, you may be able to achieve the same goals using a different design pattern.