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

Entities

As we have a lot of functionality we want to share across our data models, we'll implement an Entity base class. We need to be able to represent parent/child relationships so that a client can have supply and billing addresses. We also need to support collections of entities for our contacts and appointments. Finally, each entity hierarchy must be able to serialize itself to and from a JSON object.

Create a new class Entity in cm-lib/source/data.

entity.h:

#ifndef ENTITY_H
#define ENTITY_H
#include <map>
#include <QObject> #include <QScopedPointer>
#include <cm-lib_global.h> #include <data/data-decorator.h>
namespace cm { namespace data {
class CMLIBSHARED_EXPORT Entity : public QObject { Q_OBJECT
public: Entity(QObject* parent = nullptr, const QString& key =
"SomeEntityKey");
Entity(QObject* parent, const QString& key, const QJsonObject&
jsonObject);
virtual ~Entity();
public: const QString& key() const; void update(const QJsonObject& jsonObject); QJsonObject toJson() const;
signals: void childEntitiesChanged(); void dataDecoratorsChanged();
protected: Entity* addChild(Entity* entity, const QString& key); DataDecorator* addDataItem(DataDecorator* dataDecorator);
protected: class Implementation; QScopedPointer<Implementation> implementation; };
}} #endif

entity.cpp:

#include "entity.h"
namespace cm { namespace data {
class Entity::Implementation { public: Implementation(Entity* _entity, const QString& _key) : entity(_entity) , key(_key) { } Entity* entity{nullptr}; QString key; std::map<QString, Entity*> childEntities; std::map<QString, DataDecorator*> dataDecorators; };
Entity::Entity(QObject* parent, const QString& key) : QObject(parent) { implementation.reset(new Implementation(this, key)); }
Entity::Entity(QObject* parent, const QString& key, const QJsonObject&
jsonObject)
: Entity(parent, key) { update(jsonObject); }
Entity::~Entity() { }
const QString& Entity::key() const { return implementation->key; }
Entity* Entity::addChild(Entity* entity, const QString& key) { if(implementation->childEntities.find(key) ==
std::end(implementation->childEntities)) {
implementation->childEntities[key] = entity; emit childEntitiesChanged(); } return entity; }
DataDecorator* Entity::addDataItem(DataDecorator* dataDecorator) { if(implementation->dataDecorators.find(dataDecorator->key()) ==
std::end(implementation->dataDecorators)) {
implementation->dataDecorators[dataDecorator->key()] =
dataDecorator;
emit dataDecoratorsChanged(); } return dataDecorator; }
void Entity::update(const QJsonObject& jsonObject) { // Update data decorators for (std::pair<QString, DataDecorator*> dataDecoratorPair :
implementation->dataDecorators) {
dataDecoratorPair.second->update(jsonObject); } // Update child entities for (std::pair<QString, Entity*> childEntityPair : implementation-
>childEntities) {
childEntityPair.second>update(jsonObject.value(childEntityPair.first).toObject()); } }
QJsonObject Entity::toJson() const { QJsonObject returnValue; // Add data decorators for (std::pair<QString, DataDecorator*> dataDecoratorPair :
implementation->dataDecorators) {
returnValue.insert( dataDecoratorPair.first,
dataDecoratorPair.second->jsonValue() );
} // Add child entities for (std::pair<QString, Entity*> childEntityPair : implementation->childEntities) { returnValue.insert( childEntityPair.first, childEntityPair.second->toJson() ); } return returnValue; }
}}

Much like our DataDecorator base class, we assign all entities a unique key, which will be used in JSON serialization. We also add an overloaded constructor to which we can pass a QJsonObject so that we can instantiate an entity from JSON. On a related note, we also declare a pair of methods to serialize an existing instance to and from JSON.

Our entity will maintain a few collections—a map of data decorators representing the properties of the model, and a map of entities representing individual children. We map the key of each item to the instance.

We expose a couple of protected methods that are derived classes will use to add its data items and children; for example, our client model will add a name data item along with the supplyAddress and billingAddress children. To complement these methods, we also add signals to tell any interested observers that the collections have changed.

In both cases, we check that the key doesn’t already exist on the map before adding it. We then return the supplied pointer so that the consumer can use it for further actions. You’ll see the value of this when we come to implement the data models.

We use our populated maps for the JSON serialization methods. We’ve already declared an update() method on our DataDecorator base class, so we simply iterate through all the data items and pass the JSON object down to each in turn. Each derived decorator class has its own implementation to take care of the parsing. Similarly, we recursively call Entity::update() on each of the child entities.

Serializing to a JSON object follows the same pattern. Each data item can convert its value to a QJsonValue object, so we get each value in turn and append it to a root JSON object using the key of each item. We recursively call Entity::toJson() on each of the children, and this cascades down the hierarchy tree.

Before we can finish off our Entity, we need to declare a group of classes to represent an entity collection.