C++ Factory Pattern
Imagine a restaurant and want to order a pizza. Instead of going into the kitchen and making it yourself, you call the waiter. The waiter acts as a factory, taking your order (specifications) and returning with the desired pizza (object).
The factory pattern works similarly in software development. It's a design pattern that allows you to create objects without knowing the exact details of their creation. You simply tell the factory what you want, and it handles the rest.
The Factory Pattern is a creational design pattern that provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created.
It is particularly useful when the exact type of the object to be created is not known until runtime.
Here's an example of a real-world application of the Factory Pattern in C++.
Let's consider a scenario where you have different types of documents (e.g., PDF, HTML) and you want to create a document editor that can handle various document types.
#include <iostream>
#include <memory>
Abstract Product
class Document {
public:
virtual void open() const = 0;
virtual void save() const = 0;
virtual ~Document() = default;
};
Concrete Products
class PDFDocument : public Document {
public:
void open() const override {
std::cout << "Opening PDF document" << std::endl;
}
void save() const override {
std::cout << "Saving PDF document" << std::endl;
}
};
class HTMLDocument : public Document {
public:
void open() const override {
std::cout << "Opening HTML document" << std::endl;
}
void save() const override {
std::cout << "Saving HTML document" << std::endl;
}
};
Abstract Creator
class DocumentCreator {
public:
virtual std::unique_ptr<Document> createDocument() const = 0;
virtual ~DocumentCreator() = default;
};
Concrete Creators
class PDFDocumentCreator : public DocumentCreator {
public:
std::unique_ptr<Document> createDocument() const override {
return std::make_unique<PDFDocument>();
}
};
class HTMLDocumentCreator : public DocumentCreator {
public:
std::unique_ptr<Document> createDocument() const override {
return std::make_unique<HTMLDocument>();
}
};
Client Code
void manipulateDocument(const DocumentCreator& creator) {
std::unique_ptr<Document> document = creator.createDocument();
document->open();
document->save();
}
int main() {
// Using PDF document creator
PDFDocumentCreator pdfCreator;
manipulateDocument(pdfCreator);
std::cout << "----------------" << std::endl;
// Using HTML document creator
HTMLDocumentCreator htmlCreator;
manipulateDocument(htmlCreator);
return 0;
}
```
In this example:
- `Document` is the abstract product with `open` and `save` methods.
- `PDFDocument` and `HTMLDocument` are concrete products implementing the `Document` interface.
- `DocumentCreator` is the abstract creator with the `createDocument` method.
- `PDFDocumentCreator` and `HTMLDocumentCreator` are concrete creators creating instances of `PDFDocument` and `HTMLDocument` respectively.
The `manipulateDocument` function in the client code demonstrates how you can use different document creators interchangeably to create and manipulate documents without worrying about the specific type of document being created. This flexibility is the essence of the Factory Pattern.
Using strings to define what object needs to be created is a common variation of the Factory Pattern.
This is often referred to as the "Factory Method with Registration" or "Named Constructor Idiom." In this variation, a mapping between string identifiers and concrete classes is maintained, allowing the factory to create objects based on string input.
Here's an example to illustrate this approach:
#include <iostream>
#include <memory>
#include <unordered_map>
Abstract Product
class Document {
public:
virtual void open() const = 0;
virtual void save() const = 0;
virtual ~Document() = default;
};
Concrete Products
class PDFDocument : public Document {
public:
void open() const override {
std::cout << "Opening PDF document" << std::endl;
}
void save() const override {
std::cout << "Saving PDF document" << std::endl;
}
};
class HTMLDocument : public Document {
public:
void open() const override {
std::cout << "Opening HTML document" << std::endl;
}
void save() const override {
std::cout << "Saving HTML document" << std::endl;
}
};
Document Factory
class DocumentFactory {
public:
using CreatorFunction = std::unique_ptr<Document> (*)();
static void registerDocumentType(const std::string& type, CreatorFunction creator) {
creators()[type] = creator;
}
static std::unique_ptr<Document> createDocument(const std::string& type) {
if (creators().find(type) != creators().end()) {
return creators()[type]();
} else {
return nullptr; // or throw an exception for unknown type
}
}
private:
static std::unordered_map<std::string, CreatorFunction>& creators() {
static std::unordered_map<std::string, CreatorFunction> instance;
return instance;
}
};
Register Concrete Products
namespace {
bool registered = DocumentFactory::registerDocumentType("PDF", []() { return std::make_unique<PDFDocument>(); });
bool registeredHTML = DocumentFactory::registerDocumentType("HTML", []() { return std::make_unique<HTMLDocument>(); });
}
Client Code
void manipulateDocument(const std::string& documentType) {
std::unique_ptr<Document> document = DocumentFactory::createDocument(documentType);
if (document) {
document->open();
document->save();
} else {
std::cout << "Unknown document type: " << documentType << std::endl;
}
}
int main() {
manipulateDocument("PDF");
std::cout << "----------------" << std::endl;
manipulateDocument("HTML");
return 0;
}
In this example:
- The `DocumentFactory` class maintains a mapping between string identifiers and creator functions.
- The `registerDocumentType` method is used to register concrete creators for specific document types.
- The `createDocument` method takes a string identifier and returns an instance of the corresponding concrete product.
This approach allows you to dynamically choose which concrete class to instantiate based on runtime input. It's particularly useful when the types of objects to be created are not known until runtime or when extensibility is a key requirement.