Abstract Factory Design Pattern (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. Instead of instantiating objects directly, the creation logic is delegated to factory objects.
These factory objects are responsible for creating objects that belong to a common theme or family, ensuring that related objects are created together and remain compatible.
Why Study the Abstract Factory Pattern?
Studying the Abstract Factory design pattern can be highly beneficial for a C++ developer for several reasons:
- Code Maintenance: Simplifies system maintenance by isolating concrete classes and reducing dependencies.
- System Scalability: Allows new product families to be added without changing existing client code.
- Interchangeability: Makes it easy to swap entire product families at runtime.
- Consistency: Ensures related objects are created together and work correctly as a group.
- Product Variations: Supports multiple implementations without complicating client logic.
- Integration Ease: New product types can be integrated smoothly.
- C++ Efficiency: Centralizes object creation, encouraging better resource management.
By studying the Abstract Factory pattern in C++, developers learn how to design systems that handle growth and change gracefully, resulting in more robust and adaptable software.
Example: Domestic and Wild Animals
In this example, we use the Abstract Factory pattern to create families of animals. There are two families:
- Domestic animals (e.g., Poodle, Domestic Cat)
- Wild animals (e.g., Hyena, Lion)
The client code does not know which concrete classes are being instantiated. It interacts only with abstract interfaces.
Animal.h
#ifndef ANIMAL_H
#define ANIMAL_H
#include <string>
class Animal {
public:
virtual ~Animal() {}
virtual std::string makeSound() const = 0;
virtual std::string getName() const = 0;
};
#endif
Dog.h
#ifndef DOG_H
#define DOG_H
#include "Animal.h"
class Dog : public Animal {};
#endif
Cat.h
#ifndef CAT_H
#define CAT_H
#include "Animal.h"
class Cat : public Animal {};
#endif
Poodle.h
#ifndef POODLE_H
#define POODLE_H
#include "Dog.h"
class Poodle : public Dog {
public:
std::string makeSound() const override {
return "Woof! I'm a Poodle!";
}
std::string getName() const override {
return "Poodle";
}
};
#endif
Hyena.h
#ifndef HYENA_H
#define HYENA_H
#include "Dog.h"
class Hyena : public Dog {
public:
std::string makeSound() const override {
return "Laugh! I'm a Hyena!";
}
std::string getName() const override {
return "Hyena";
}
};
#endif
DomesticCat.h
#ifndef DOMESTICCAT_H
#define DOMESTICCAT_H
#include "Cat.h"
class DomesticCat : public Cat {
public:
std::string makeSound() const override {
return "Meow! I'm a Domestic Cat!";
}
std::string getName() const override {
return "Domestic Cat";
}
};
#endif
Lion.h
#ifndef LION_H
#define LION_H
#include "Cat.h"
class Lion : public Cat {
public:
std::string makeSound() const override {
return "Roar! I'm a Lion!";
}
std::string getName() const override {
return "Lion";
}
};
#endif
AnimalFactory.h
#ifndef ANIMALFACTORY_H
#define ANIMALFACTORY_H
#include "Dog.h"
#include "Cat.h"
class AnimalFactory {
public:
virtual Dog* createDog() const = 0;
virtual Cat* createCat() const = 0;
virtual ~AnimalFactory() {}
};
#endif
DomesticAnimalFactory.h
#ifndef DOMESTICANIMALFACTORY_H
#define DOMESTICANIMALFACTORY_H
#include "AnimalFactory.h"
#include "Poodle.h"
#include "DomesticCat.h"
class DomesticAnimalFactory : public AnimalFactory {
public:
Dog* createDog() const override {
return new Poodle();
}
Cat* createCat() const override {
return new DomesticCat();
}
};
#endif
WildAnimalFactory.h
#ifndef WILDANIMALFACTORY_H
#define WILDANIMALFACTORY_H
#include "AnimalFactory.h"
#include "Hyena.h"
#include "Lion.h"
class WildAnimalFactory : public AnimalFactory {
public:
Dog* createDog() const override {
return new Hyena();
}
Cat* createCat() const override {
return new Lion();
}
};
#endif
main.cpp
#include <iostream>
#include <memory>
#include "DomesticAnimalFactory.h"
#include "WildAnimalFactory.h"
int main() {
auto domesticFactory = std::make_unique<DomesticAnimalFactory>();
auto domesticDog = std::unique_ptr<Dog>(domesticFactory->createDog());
auto domesticCat = std::unique_ptr<Cat>(domesticFactory->createCat());
std::cout << domesticDog->makeSound() << " I am a " << domesticDog->getName() << std::endl;
std::cout << domesticCat->makeSound() << " I am a " << domesticCat->getName() << std::endl;
auto wildFactory = std::make_unique<WildAnimalFactory>();
auto wildDog = std::unique_ptr<Dog>(wildFactory->createDog());
auto wildCat = std::unique_ptr<Cat>(wildFactory->createCat());
std::cout << wildDog->makeSound() << " I am a " << wildDog->getName() << std::endl;
std::cout << wildCat->makeSound() << " I am a " << wildCat->getName() << std::endl;
return 0;
}
S.W.O.T. Analysis
Strengths
- Decouples client code from concrete implementations
- Ensures consistency across product families
- Improves scalability and extensibility
Weaknesses
- Introduces additional classes and interfaces
- Can increase overall system complexity
Opportunities
- Leverages modern C++ features like smart pointers
- Combines well with other design patterns
Threats
- Risk of overengineering for small systems
- Potential performance overhead due to abstraction