Classes

Basic interfaces and implementations for abstract definition of the data model. More...

Classes

class  ibase::CObjectSynchronizerComp
 Component for synchronization between some master data object and its "slaves". More...
 
class  ibase::TMakeModelObserverCompWrap< Base, Interface1, Interface2, Interface3, Interface4, Interface5, Interface6, Interface7, Interface8, Interface9, Interface10 >
 Cretion of wrapper for model observer components from non component classes. More...
 
class  ibase::TModelObserverCompBaseWrap< ObserverComponent >
 Generic basic implementation of component wrapper for model observer classes. More...
 
class  istd::CChangeDelegator
 Delegates calls of IChangeable methods to the given slave. More...
 
class  istd::CChangeGroup
 Help class which provides the group of changes for update mechanism of the model. More...
 
class  istd::CChangeNotifier
 Help class which provides the automatic update mechanism of the model. More...
 
class  istd::CEventBasedNotifier
 Implementation of model changes notification between different threads. More...
 
class  istd::IChangeable
 Common interface for data model objects, which can be changed. More...
 
class  istd::IChangeDelegator
 Common interface for all classes that supports delegation of their data updates to another class. More...
 
class  istd::TChangeDelegator< Base >
 Binder of some istd::IChangeable implementation and changing delegator. More...
 

Detailed Description

Basic interfaces and implementations for abstract definition of the data model.

Motivation

A fundamental problem in the implementation of complex software applications is ensuring a clean separation between the data model, business logic (controller) and data presentation (GUI). Such a separation allows a high degree of reusability of source code. The following aspects have to be represented by interfaces:

Overview

The most important interface for a general data model definition is istd::IChangeable. This is a common interface for describing of objects which change their state during the run time of the application. The interface provides methods for managing data change transaction (istd::IChangeable::BeginChanges and istd::IChangeable::EndChanges), methods for coping, cloning, reseting and comparison of objects. The realization of change notification mechanism is also based on this interface. Following example demonstrates implementation of a simple data object:

class CPerson: virtual public istd::IChangeable
{
public:
enum ChangeFlags
{
CF_NAME_CHANGED = 0x74b520 // Some random, unique number
}
CPerson(const QString& firstName = QString(), const QString& lastName = QString());
CPerson(const CPerson& person);
QString GetFirstName() const;
void SetFirstName(const QString& firstName);
QString GetLastName() const;
void SetLastName(const QString& name);
private:
QString m_firstName;
QString m_lastName;
};
QString CPerson::GetFirstName() const
{
return m_firstName;
}
QString CPerson::SetFirstName(const QString& firstName)
{
if (m_firstName != firstName){
static ChangeSet changeSet(CF_NAME_CHANGED, "Change first name");
BeginChanges(changeSet);
m_firstName = firstName;
EndChanges(changeSet);
}
}
QString CPerson::GetLastName() const
{
return m_lastName;
}
QString CPerson::SetLastName(const QString& lastName)
{
if (m_lastName != lastName){
static ChangeSet changeSet(CF_NAME_CHANGED, "Change last name");
BeginChanges(changeSet);
m_lastName = lastName;
EndChanges(changeSet);
}
}

To ensure that the transaction block is always consistent, you could also use a helper class - istd::CChangeNotifier:

QString CPerson::SetFirstName(const QString& firstName)
{
if (m_firstName != firstName){
static ChangeSet changeSet(CF_NAME_CHANGED, "Change first name");
istd::CChangeNotifier notifier(this, &changeSet);
Q_UNUSED(notifier);
m_firstName = firstName;
}
}

istd::CChangeNotifier calls BeginChanges in its constructor and EndChanges in the destructor.

Why do we need begin-end notification of the changes of data?

Often you want to be informed about the upcoming changes. A simple example you want to save the existing data before it is overwritten with the changed data. This is responsibility of the istd::IChangeable::BeginChanges method. The end change notification you will need, if you want to know when the data changes are complete, for example to update a GUI.

Delegating of changes

An important aspect in the management of data change notifications is the delegating of changes from a part of data to another. Let us consider the following situation - the data object of class CPerson could "live" in any container class (eg. in a database). In this case we want the container implentation will also notice about the changes of the CPerson instance. What we have to do is to extend our CPerson implementation as following:

class CPerson: public istd::TChangeDelegator<istd::IChangeable>

Then our container class can be defined as:

class CPersonDatabase: virtual public istd::IChangeable
{
public:
int GetPersonsCount() const
{
return m_persons.count();
}
const CPerson& GetPerson(int personIndex) const
{
return m_persons[personIndex];
}
CPerson& AddNewPerson(const QString& firstName, const QString& lastName);
protected:
// reimplemented (istd::IChangeable)
virtual void OnEndChanges(const ChangeSet& changeSet)
private:
QVector<CPerson> m_persons;
};
CPerson& CPersonDatabase::AddNewPerson(const QString& firstName, const QString& lastName)
{
CPerson person(firstName, lastName);
m_persons.push_back(person);
// Register the container as change notification target:
m_persons.back().SetSlavePtr(this);
return m_persons.back();
}

Now we can catch the changes of person instances in the implementation of the method OnEndChanges():

// reimplemented (istd::IChangeable)
void CPersonDatabase::OnEndChanges(const ChangeSet& changeSet)
{
// Use CF_DELEGATED masking to filter out the delegated changes:
if (changeSet.Contains(CF_DELEGATED)){
// We will end up here, every time when CPerson::SetFirstName or CPerson::SetLastName were called:
qDebug("Some person data have been changed");
}
}

In this section we have considered situations in which a decision that a data model would delegate its changes has been made in the design phase. For situations where this is not the case, we must rely on other mechanisms. These are described in the Model/Observer concept Section.

© 2007-2017 Witold Gantzke and Kirill Lepskiy