End to End GUI Development with Qt5
上QQ阅读APP看书,第一时间看更新

Object factory

In a larger system with more comprehensive MasterController tests in place, having all of that object creation hard-coded inside the private implementation will cause problems because of the tight coupling between the MasterController and its dependencies. One option will be to create all the other objects in main() instead and inject them into the MasterController constructor as we have done with the other controllers. This will mean injecting a lot of constructor parameters, and it is handy to be able to keep the MasterController instance as the parent of all the other objects, so we will inject a single object factory that the controller can use for all of its object creation needs instead.

The critical part of this factory pattern is to hide everything behind interfaces, so when testing MasterController, you can pass in a mock factory and control all the object creation. In cm-lib, create a new i-object-factory.h header file in source/framework:

#ifndef IOBJECTFACTORY_H
#define IOBJECTFACTORY_H

#include <controllers/i-command-controller.h>
#include <controllers/i-database-controller.h>
#include <controllers/i-navigation-controller.h>
#include <models/client.h>
#include <models/client-search.h>
#include <networking/i-network-access-manager.h>
#include <networking/i-web-request.h>

namespace cm {
namespace framework {

class IObjectFactory
{
public:
    virtual ~IObjectFactory(){}

    virtual models::Client* createClient(QObject* parent) const = 0;
    virtual models::ClientSearch* createClientSearch(QObject* parent, controllers::IDatabaseController* databaseController) const = 0;
    virtual controllers::ICommandController* createCommandController(QObject* parent, controllers::IDatabaseController* databaseController, controllers::INavigationController* navigationController, models::Client* newClient, models::ClientSearch* clientSearch, networking::IWebRequest* rssWebRequest) const = 0;
    virtual controllers::IDatabaseController* createDatabaseController(QObject* parent) const = 0;
    virtual controllers::INavigationController* createNavigationController(QObject* parent) const = 0;
    virtual networking::INetworkAccessManager* createNetworkAccessManager(QObject* parent) const = 0;
    virtual networking::IWebRequest* createWebRequest(QObject* parent, networking::INetworkAccessManager* networkAccessManager, const QUrl& url) const = 0;
};

}}

#endif

All the objects we will create will be moved behind interfaces apart from the models. This is because they are essentially just data containers, and we can easily create real instances in a test scenario with no side effects.

We will skip that exercise here for brevity and leave it as an exercise for the reader. Use IDatabaseController as an example or refer to the code samples.

With the factory interface available, change the MasterController constructor to take an instance as a dependency:

MasterController::MasterController(QObject* parent, IObjectFactory* objectFactory)
    : QObject(parent)
{
    implementation.reset(new Implementation(this, objectFactory));
}

We pass the object through to Implementation and store it in a private member variable as we have done numerous times before. With the factory available, we can now move all the new based object creation statements into a concrete implementation of the IObjectFactory interface (the ObjectFactory class) and replace those statements in MasterController with something more abstract and testable:

Implementation(MasterController* _masterController, IObjectFactory* _objectFactory)
    : masterController(_masterController)
    , objectFactory(_objectFactory)
{
    databaseController = objectFactory->createDatabaseController(masterController);
    clientSearch = objectFactory->createClientSearch(masterController, databaseController);
    navigationController = objectFactory->createNavigationController(masterController);
    networkAccessManager = objectFactory->createNetworkAccessManager(masterController);
    rssWebRequest = objectFactory->createWebRequest(masterController, networkAccessManager, QUrl("http://feeds.bbci.co.uk/news/rss.xml?edition=uk"));
    QObject::connect(rssWebRequest, &IWebRequest::requestComplete, masterController, &MasterController::onRssReplyReceived);
    newClient = objectFactory->createClient(masterController);
    commandController = objectFactory->createCommandController(masterController, databaseController, navigationController, newClient, clientSearch, rssWebRequest);
}

Now, when testing MasterController, we can pass in a mock implementation of the IObjectFactory interface and control the creation of objects. In addition to implementing ObjectFactory and passing it to MasterController when we instantiate it, one further change is that in main.cpp, we now need to register the interfaces to NavigationController and CommandController, rather than the concrete implementations.  We do this by simply swapping out the qmlRegisterType statements with the qmlRegisterUncreatableType companion:

qmlRegisterUncreatableType<cm::controllers::INavigationController>("CM", 1, 0, "INavigationController", "Interface");
qmlRegisterUncreatableType<cm::controllers::ICommandController>("CM", 1, 0, "ICommandController", "Interface");