Design Pattern - Factory
The factory method design pattern is one of the well-known?GoF?design patterns. It is a creational design pattern that uses factory methods to deal with creating instances without specifying the exact class of the object that will be created.
Factory Method is a creational design pattern that defines an interface or abstract class for creating an object, but allows subclasses to decide the type of objects that will be instantiated.
This pattern is named as a?factory?as it is responsible for manufacturing an Object. The factory design pattern is predicated upon an object-oriented encapsulation idea. In general, Object creation code is written on client side of the program, but with the factory design, we encapsulate object creation code inside the factory method. Therefore, the factory may return an object of one of the various classes mentioned within a program, based on the data supplied to it.
I recommend reading this article on my Dev community page or GitHub page for better reading experience
Motivation
Let's look at a scenario where there is a need to create different types of objects with varying configurations, but the exact type of object needed may not be known until runtime.
In the case of a computer game, the game may need to create different types of characters with varying abilities and characteristics. However, the exact type of character needed may not be known until runtime, as it may depend on factors such as the player's progress in the game or the level they are currently playing.
If we were to directly create objects using concrete classes for each character type, we would end up with code duplication and tightly coupled code. As the number of character types increases, managing the creation logic becomes cumbersome and error-prone. Additionally, modifying or adding new character types would require modifying existing client code, violating the principle of Open-Closed principle.
To address these challenges, we can apply the Factory Design Pattern. The Factory Design Pattern provides a structured approach to object creation, encapsulating the creation process within a factory class and promoting loose coupling between the client code and the concrete classes.
!Factory Design Pattern
Let's look at the below C++ example that doesn't use factory design pattern and analyze the potential problems.
#include <string>
// Character base class
class Character
{
public:
? ? virtual void displayInfo() const = 0;
? ? virtual void attack() const = 0;
? ? virtual void defend() const = 0;
? ? virtual ~Character() {}
};
// Concrete character classes
class Warrior : public Character
{
public:
? ? void displayInfo() const override
? ? {
? ? ? ? std::cout << "Warrior: A strong melee fighter with high stamina." << std::endl;
? ? }
? ? void attack() const override
? ? {
? ? ? ? std::cout << "Warrior attacks with a mighty swing!" << std::endl;
? ? }
? ? void defend() const override
? ? {
? ? ? ? std::cout << "Warrior raises a sturdy shield to block incoming attacks!" << std::endl;
? ? }
};
class Mage : public Character
{
public:
? ? void displayInfo() const override
? ? {
? ? ? ? std::cout << "Mage: A magical spellcaster with powerful elemental abilities." << std::endl;
? ? }
? ? void attack() const override
? ? {
? ? ? ? std::cout << "Mage casts a powerful fireball!" << std::endl;
? ? }
? ? void defend() const override
? ? {
? ? ? ? std::cout << "Mage conjures a protective barrier to deflect enemy spells!" << std::endl;
? ? }
};
class Archer : public Character
{
public:
? ? void displayInfo() const override
? ? {
? ? ? ? std::cout << "Archer: A skilled marksman with exceptional ranged attacks." << std::endl;
? ? }
? ? void attack() const override
? ? {
? ? ? ? std::cout << "Archer fires a precise arrow at the target!" << std::endl;
? ? }
? ? void defend() const override
? ? {
? ? ? ? std::cout << "Archer swiftly evades incoming attacks with precise movements!" << std::endl;
? ? }
};
// Client code
int main()
{
? ? Character *warrior = new Warrior();
? ? Character *mage = new Mage();
? ? Character *archer = new Archer();
? ? warrior->displayInfo();
? ? warrior->attack();
? ? warrior->defend();
? ? mage->displayInfo();
? ? mage->attack();
? ? mage->defend();
? ? archer->displayInfo();
? ? archer->attack();
? ? archer->defend();
? ? delete warrior;
? ? delete mage;
? ? delete archer;
? ? return 0;
}
In this example, characters are created directly in the client code without using the Factory Design Pattern. Each character type (Ex,?Warrior,?Mage,?Archer) is instantiated using the concrete class constructors. The member functions simulate the specific actions that each character type can perform during the game.
Problems in the example
The potential problems include:
By using the Factory Design Pattern, we can address these problems by encapsulating the creation logic within a factory class, promoting loose coupling, providing an abstraction layer, and facilitating extensibility without modifying the client code.
领英推荐
The Solution
The Factory Method pattern suggests substituting direct object construction calls (made with the?new?operator) with invocations of a dedicated factory method. This provides a level of indirection between the client code and the actual creation of objects.
#include <string>
// Character base class
class Character
{
public:
? ? virtual void displayInfo() const = 0;
? ? virtual void attack() const = 0;
? ? virtual void defend() const = 0;
? ? virtual ~Character() {}
};
// Concrete character classes
class Warrior : public Character
{
public:
? ? void displayInfo() const override
? ? {
? ? ? ? std::cout << "Warrior: A strong melee fighter with high stamina." << std::endl;
? ? }
? ? void attack() const override
? ? {
? ? ? ? std::cout << "Warrior attacks with a mighty swing!" << std::endl;
? ? }
? ? void defend() const override
? ? {
? ? ? ? std::cout << "Warrior raises a sturdy shield to block incoming attacks!" << std::endl;
? ? }
};
class Mage : public Character
{
public:
? ? void displayInfo() const override
? ? {
? ? ? ? std::cout << "Mage: A magical spellcaster with powerful elemental abilities." << std::endl;
? ? }
? ? void attack() const override
? ? {
? ? ? ? std::cout << "Mage casts a powerful fireball!" << std::endl;
? ? }
? ? void defend() const override
? ? {
? ? ? ? std::cout << "Mage conjures a protective barrier to deflect enemy spells!" << std::endl;
? ? }
};
class Archer : public Character
{
public:
? ? void displayInfo() const override
? ? {
? ? ? ? std::cout << "Archer: A skilled marksman with exceptional ranged attacks." << std::endl;
? ? }
? ? void attack() const override
? ? {
? ? ? ? std::cout << "Archer fires a precise arrow at the target!" << std::endl;
? ? }
? ? void defend() const override
? ? {
? ? ? ? std::cout << "Archer swiftly evades incoming attacks with precise movements!" << std::endl;
? ? }
};
// CharacterFactory class
class CharacterFactory
{
public:
? ? static Character *createCharacter(const std::string &characterType)
? ? {
? ? ? ? if (characterType == "Warrior")
? ? ? ? {
? ? ? ? ? ? return new Warrior();
? ? ? ? }
? ? ? ? else if (characterType == "Mage")
? ? ? ? {
? ? ? ? ? ? return new Mage();
? ? ? ? }
? ? ? ? else if (characterType == "Archer")
? ? ? ? {
? ? ? ? ? ? return new Archer();
? ? ? ? }
? ? ? ? else
? ? ? ? {
? ? ? ? ? ? std::cout << "Invalid character type. Creating a default character." << std::endl;
? ? ? ? ? ? return new Warrior(); // Default to a Warrior character
? ? ? ? }
? ? }
};
// Client code
int main()
{
? ? // Create a Warrior character
? ? Character *warrior = CharacterFactory::createCharacter("Warrior");
? ? warrior->displayInfo();
? ? warrior->attack();
? ? warrior->defend();
? ? // Create a Mage character
? ? Character *mage = CharacterFactory::createCharacter("Mage");
? ? mage->displayInfo();
? ? mage->attack();
? ? mage->defend();
? ? // Create an Archer character
? ? Character *archer = CharacterFactory::createCharacter("Archer");
? ? archer->displayInfo();
? ? archer->attack();
? ? archer->defend();
? ? // Clean up the memory
? ? delete warrior;
? ? delete mage;
? ? delete archer;
? ? return 0;
}
This code demonstrates the use of the Factory Design Pattern to create different types of characters (Warrior,?Mage,?Archer) based on the character type provided. The?CharacterFactory?class acts as the factory responsible for creating character objects. The?createCharacter?static method in the factory takes a character type as input and returns a dynamically allocated instance of the corresponding concrete character class.
In the client code, various character objects are created using the?CharacterFactory::createCharacter?method and then utilized by calling their respective member functions (displayInfo,?attack,?defend). Finally, the dynamically allocated character objects are deleted to release the memory.
Here is a UML class diagram representing the example code:
In this UML class diagram:
This approach offers several advantages.
How to implement?
Conclusion
The Factory Design Pattern provides a way to create objects without specifying their exact types upfront. By using a factory class, you can centralize object creation, enhance code modularity, and facilitate runtime determination of object types. The pattern is valuable for scenarios where object creation involves complex logic, varying dependencies, or the need to conserve system resources. Overall, the Factory Design Pattern improves code flexibility, maintainability, and scalability.