Search code examples
c++design-patternsfactory-pattern

Is "Modern C++ Design" DocumentManager design correct?


I was going through the "Modern C++ Design" book, I saw the below code explained in "8.1 The Need for Object Factories", and I have some doubts.

  1. Is my understanding that, "CreateDocument()" should be overridden in a new derivedDocumentManager class each time a derivedDocument class is writen?
  2. If Yes, then there will be way too many derived documentManagers and needs its own Factory method!!!
  3. If No, then "CreateDocument()" should take an ID so that it can decide exactly what to create among a of objects. But this also means, each time a derivedDocument is created, he also should find the correct documentManager and update the CreateDocument() method. But we have to decide to have or not to have the a Factory of documentManagers as there can be few or many of them.

    My main doubt is whether this is a solution at all or if I am missing the point. According to the book, CreateDocument() is the GoF book's factory method. "CreateDocument()" creating a new derivedDocument based on an ID and lots of conditions at least makes sense. But lots of derivedDocumentManager does not make sense.

DocumentManager from the book,

class DocumentManager
{
    ...
public:
    Document* NewDocument();
private:
    virtual Document* CreateDocument() = 0;
    std::list<Document*> listOfDocs_;
};

Document* DocumentManager::NewDocument()
{
    Document* pDoc = CreateDocument();
    listOfDocs_.push_back(pDoc);
    ...
    return pDoc;
}

Client code:

Document* GraphicDocumentManager::CreateDocument()
{
    return new GraphicDocument;
}

Solution

  • UPDATE: your question's been heavily edited to give it a different focus. A few more points:

    • You seem to be thinking that any mention of "factory" necessarily involves one concrete function accepting some input and spitting out an instance of one of many possible dynamic types based on the input. That is just one type of factory, and not the type presented in your code.

    Have a read about the Factory Method Pattern and hopefully you'll realise that's what you have. It lets you do things like:

    std::string compress(DocumentManager& d, const std::string& uncompressed)
    {
        std::unique_ptr<Document> doc = std::make_unique(d.NewDcoument());
        doc = uncompressed;  // interpret to form document of whatever type
        return zlib::compress(d.data(), d.size());  // compress as binary blob
    }
    

    Here, the one compress() function can be called with some raw input and attempt to first create a document of the right caller-nominated type then stuff some data into it and compress it....

    The factory aspect is the ability of compress to create an object without knowing the concrete type involved, as - being untemplated and lacking any switching - it can't otherwise choose between many constructors.


    Answer based on original questions...

    Are we not moving the problem of object creation from Document to DocumentManager class. I mean we have to create [concrete] DocumentManager?

    That's the point isn't it - to have extra record-keeping (in this case the list<Document*>) around the creation of specific types of Documents.

    If we let client code create Documents directly, having such a list would depend on the client updating the list every time they create an object (or require invasive changes to the Document constructors). If we wanted to say add timestamps for when the object creations we'd have to modify every place in the client code that creates any type of Document.

    If we didn't have a DocumentManager base class, we'd have to put similar logic and data members into any number of GraphicDocumentManager, TextDocumentManager, AudioDocumentManager etc., and couldn't handle those managers polymorphically (e.g. creating a vector<DocumentManager*>, writing functions ala void f(DocumentManager&);.

    How to keep track of all the derived classes of DocumentManager, created by many independent clients?

    There's nothing in the design intended to do that, and C++ doesn't have introspective facilities that would let you enumerate the derived types - neither at compile-time nor when the program starts running. But, if you're prepared to wait for NewDocument to be called you could record the addresses of concrete DocumentManager-derived objects and/or access their RTTI information (which would e.g. let you count the distinct types of documents that had been created, or try displaying the implementation-defined (and possibly empty) name() field of the dynamic types....

    Should not the listOfDocs_ be static and DocumentManager be a singleton.

    Probably not. The program might want to do something like keep a DocumentManager object per TCP client, per filesystem, per user etc. - so why limit it unnaturally? It's easy and better to make a more flexible DocumentManager type, then let client code apply a singleton wrapper (e.g. function returning a static instance) if that's actually useful for them. That also makes it easier to test: you can freely create DocumentManagers, run a test, let destructors run, create another etc....

    My main doubt is, is that a solution at all or I am missing some point.

    Seems you're missing something "bit picture", but without the book to hand or more general question/statements-of-understand from you it's hard to know what else it might be.

    According to the book "CreateDocument()" is the GoF book's factory method. But I am not able to understand the above points.

    Yes - that's a factory method, as it creates any of many different dynamic types of objects, but returns a pointer-to-base-class by which they can be handled polymorphically.