Monday, December 4, 2023

C++ Factory Pattern

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.


No comments:

Post a Comment

LeetCode C++ Cheat Sheet June

🎯 Core Patterns & Representative Questions 1. Arrays & Hashing Two Sum – hash map → O(n) Contains Duplicate , Product of A...