Search code examples
c++design-patternsfactorybuilder

Confused about the appropriate design pattern to be used here


I am implementing a class that can download a file from various sources such as ftp, http etc. I started with the following interface

class IInternetFileDownloader
{
public:
    IInternetFileDownloader() = default;

    virtual void Download(const std::string& urlToDownloadFrom) = 0;

};

I then implemented the classes that will perform the actual download from the appropriate endpoint. So I have a HttpFileDownloader.h as follows

#include "IInternetFileDownloader.h"
class HttpFileDownloader : public IInternetFileDownloader
{
public:
    HttpFileDownloader() = default;

    virtual void Download(const std::string& urlToDownloadFrom)
    {
        // download implementation
    }

};

So I have a FtpFileDownloader .h as follows

#include "IInternetFileDownloader.h"
class FtpFileDownloader : public IInternetFileDownloader
{
public:
    FtpFileDownloader() = default;

    virtual void Download(const std::string& urlToDownloadFrom)
    {
        // download implementation
    }

};

I can invoke the appropriate class as below

#include "IInternetFileDownloader.h"
#include "HttpFileDownloader.h"

int main()
{
    std::string downloadFromUrl = "http://www.example.org/xyz.zip";
    IInternetFileDownloader* download = new HttpFileDownloader();
    download->Download(downloadFromUrl);
}

However, I don't want to instantiate specific HttpFileDownloader or FtpFileDownloader here. In my mind, there should be another class that can just take the url and depending upon the protocol, it can construct the appropriate class. This way the client code (main.cpp) does not need to worry about the instantiation of the appropriate class. I read about the factory and builder design patterns and a little confused about which one will be best to use in this scenario?


Solution

  • Simplest approach would be to have a static function on IInternetFileDownloader to instantiate the correct subclass.

    Also, I don't think you need that default constructors in the base class, but you likely need a default destructor in the base class that is virtual. That way, your factory function, CreateDownloader, that I propose, can return a pointer (or shared_ptr) to an IInternetFileDownloader instance that you delete later.

    class IInternetFileDownloader
    {
    public:
        virtual ~IInternetFileDownloader() = default;
        virtual void Download(const std::string& urlToDownloadFrom) = 0;
    
        // parses the url to infer the protocol and construct an instance of a derived class
        static IInternetFileDownloader* CreateDownloader(const std:string& url);
    };
    
    class HttpFileDownloader : public HttpFileDownloader 
    {
    public:
        virtual void Download(const std::string& urlToDownloadFrom) override;
    };
    
    class FtpFileDownloader : public IInternetFileDownloader
    {
    public:
        virtual void Download(const std::string& urlToDownloadFrom) override;
    };
    

    And that's probably the approach I'd go with.

    Another variant of this is that it might make sense to have the "factory" as a separate class. One advantage is that it might enable a better ability to mock an instance of a downloader in a unit test.

    class IInternetFileDownloader
    {
    public:
        virtual ~IInternetFileDownloader() = default;
        virtual void Download(const std::string& urlToDownloadFrom) = 0;
    };
    
    class InternetFileDownloaderFactory
    {
    public:
        // parses the url to infer the protocol and construct an instance of a derived class
        virtual IInternetFileDownloader* CreateDownloader(const std:string& url);
    };
    
    class InternetFileDownloaderFactoryMock : public InternetFileDownloaderFactory
    {
    public:
        IInternetFileDownloader* CreateDownloader(const std:string& url) override
        {
             return new MockFileDownloaderFactoryMock();
        }
    };
    
    class MockFileDownloaderFactoryMock : public IInternetFileDownloader
    {
    public:
        virtual void Download(const std::string& urlToDownloadFrom)
        {
           // do nothing or simulate a download
        }
    };