V. L'entrelacement des données▲
Super, nous avons nos données, chargeables, utilisables puis "mise-à-jourable" et supprimable, connectées à plusieurs sources de données. Jusque là, elles se gèrent de la même manière, mais ne se "connaissent pas".
En effet, c'est facile de charger les livres, les mettre à jour ou les supprimer, en revanche, si nous souhaitons, pour un auteur donné obtenir la liste de ses livres parus, ou récupérer l'auteur d'un livre particulier, c'est une autre affaire. Actuellement nous pouvons le faire, de cette manière :
// Récupère l'auteur d'un livre
CAuthor*
GetAuthorFromBook(CBook*
pBook)
{
CDataIterator<
CAuthor>
it;
CAuthor*
pCurrentAuthor =
0
;
while
((pCurrentAuthor =
it.GetData()) !=
0
)
{
if
(pBook->
GetAuthorIdent() ==
pCurrentAuthor.GetIdent())
return
pBook;
it.Next();
}
return
0
;
}
;
std::
vector<
CBook*>
GetBooksFromCustomers(CCustomers*
pCustomers)
{
std::
vector<
CBook*>
vecBooks;
CDataIterator<
CBooksCustomers>
it;
CAuthor*
pCurrentAuthor =
0
;
while
((CBooksCustomers =
it.GetData()) !=
0
)
{
if
(pCurrentAuthor->
GetCustomerIdent() ==
pCustomers->
GetIdent())
{
// Nous récupérons le livre, soit nous parcourrons
// soit nous utilisons la méthode de recherche
std::
map<
std::
string, IProperty&>&
mapIdents;
mapIdents["ID"
] =
pCurrentAuthor->
GetBookIdent();
vecBooks.push_back(GesMgr.GetFromIdents(mapIdents));
}
it.Next();
}
}
Voici les contraintes que ça nous impose :
- Le code est moche, ça peut paraître un détail mais sur un gros projet, c'est fatiguant et très lassant à l'oeil
- C'est peu performant. En effet, si nous avons déjà récupéré l'auteur de Candide, c'est bête (et moche) de devoir refaire le parcours
- Nous imposons du bas niveau dans le haut niveau, donc nous mélangeons et l'ensemble de la solution en prend un coup
- Et surtout, nous faisons quelque chose censé gérer des données... si nous ne proposons pas quelque chose pour gérer ce genre de besoin primaire, nous allons passer pour des rigolos.
Le développeur utilisateur lui, il veut ceci :
CBook*
pBook =
[...];
CAuthor*
pAuthor =
pBook->
GetAuthor();
CUneListe&
listDesLibres =
pAuthor->
GetBooks(); // avec un p'tit itérateur pour le parcourt biensûr ;)
C'est parti pour les relations.
V-A. Les relations 1-1▲
Voici une relation 1-1 :
Ou via un intermédiaire :
Nous avons donc besoin d'un objet, se comportant et s'utilisant comme un pointeur. Pour se faire, inspirons nous des pointeurs intelligents (Smart Pointer), utilisés pour gérer des ressources partagées dont il est difficile de savoir à quel moment elle ne sont plus utilisées par personne et par conséquent le moment où nous pouvons les détruire. Ce ne sont pas de "vrais" pointeurs, mais des objets encapsulant un pointeur avec une petite mécanique permettant de compter les références (utilisateurs). Lorsque cet objet détecte que plus personne ne l'utilise, le pointeur est détruit, sans appel explicite de l'extérieur. Pour leur donner une "apparence" de pointeur, les opérateurs * et -> sont redéfinis, ainsi qu'une conversion implicite en son type pointé. Nous allons donc faire pareil, mais au lieu de gérer de la mémoire, nous gèrerons une relation. Appelons les "les pointeurs de données intelligent"
Comme nous n'avons pas forcément besoin d'avoir systématiquement la réconciliation (connaître tous les auteurs de tous les livres par exemple), nous la ferons sur demande. Enfin, afin de préserver la cohérence des données, nous devront écouter les fluctuations de celle-ci pour détecter un besoin de "casser" la relation, qui sera reconstruite, toujours sur demande.
Dans notre pointeur, nous devons posséder :
- Le référenceur, c'est à dire le livre si le but est de le réconcilier avec l'auteur.
- Le filtre (interface IBinaryFilter) sur lequel nous nous appuierons pour savoir si l'auteur est le bon ou pas pour le livre donné.
- Le référencé, pour stocker l'auteur une fois ce dernier retrouvé.
Le filtre sera donc binaire puisqu'il stockera les propriétés à comparer, puis sa méthode IsOk prendra deux objets (par exemple un livre et un auteur) et indiquera si le couple correspond. Voyons à quoi ils ressemblerons :
/*!
*
\brief
Représente un filtre binaire.
*/
class
IBinaryFilter
{
public
:
/*!
*
\brief
Indique si un couple de donnée "passe" ce filtre.
*
*
\param
[in] poLeft L'opérande de gauche.
*
\param
[in] poRight L'opérande de droite.
*
\return
true si ça passe, sinon false.
*/
virtual
bool
IsOk(IData*
poLeft, IData*
poRight) const
=
0
;
}
;
Le filtre peut également changer, il doit dont pouvoir le notifier. Que va-t-il notifier ? En fait, il doit pouvoir indiquer le fait qu'une nouvelle propriété, que ça soit de la partie gauche ou de la partie droite, rentre dans le test de filtre (par exemple si l'identifiant du livre change, ou celui de l'auteur). Pour se faire, il doit maintenir une liste de propriété des deux côtés, avec un compteur de référence.
class
IBinaryFilter
{
public
:
/*!
*
\brief
Type des propriétés écoutées, avec leur occurence.
*/
typedef
std::
map<
std::
string, int
>
TMapProperties;
TMapProperties m_mapLeftProperties; /*!
< les propriétés écoutées sur l'opérande de gauche.
*/
TMapProperties m_mapRightProperties; /*!
< les propriétés écoutées sur l'opérande de droite.
*/
}
;
Ainsi les filtres "liste" (AND, OR, perso métier), lors de l'ajout de filtres composant, rempliront ces dictionnaires, puis notifierons l'ajout de propriétés filtrés. Les utilisateurs de ces filtres pourrons alors être averti que le filtre a "changé" et qu'il faut qu'elle vérifie leur cohérence.
Voyons l'écouteur :
/*!
*
\brief
Interface d'écoute d'un filtre binaire.
*/
class
IBinaryFilterListener
{
friend
class
IBinaryFilter;
protected
:
/*!
*
\brief
Une propriété de l'opérande de gauche du filtre est désormais ou à nouveau filtrée.
*
*
\param
[in] rstrLeftProperty Le nom de la propriété.
*
\param
[in] poFilter Le filtre à l'origine de cet évennement.
*/
virtual
void
OnBinaryFilteredLeftPropertyAdded(const
IBinaryFilter*
poFilter, const
std::
string&
rstrLeftProperty) =
0
;
/*!
*
\brief
Une propriété de l'opérande de gauche n'est plus filtrée par un composant du filtre.
*
*
\param
[in] rstrLeftProperty Le nom de la propriété.
*
\param
[in] poFilter Le filtre à l'origine de cet évennement.
*/
virtual
void
OnBinaryFilteredLeftPropertyRemoved(const
IBinaryFilter*
poFilter, const
std::
string&
rstrLeftProperty) =
0
;
/*!
*
\brief
Une propriété de l'opérande de droite du filtre est désormais ou à nouveau filtrée.
*
*
\param
[in] rstrRightProperty Le nom de la propriété.
*
\param
[in] poFilter Le filtre à l'origine de cet évennement.
*/
virtual
void
OnBinaryFilteredRightPropertyAdded(const
IBinaryFilter*
poFilter, const
std::
string&
rstrRightProperty) =
0
;
/*!
*
\brief
Une propriété de l'opérande de droite n'est plus filtrée par un composant du filtre.
*
*
\param
[in] rstrRightProperty Le nom de la propriété.
*
\param
[in] poFilter Le filtre à l'origine de cet évennement.
*/
virtual
void
OnBinaryFilteredRightPropertyRemoved(const
IBinaryFilter*
poFilter, const
std::
string&
rstrRightProperty) =
0
;
}
;
Puis le branchement
class
IBinaryFilter
{
public
:
/*!
*
\brief
Ajoute un écouteur à cette liste.
*
*
\param
poListener L'écouteur à ajouter.
*/
void
AddListener(IBinaryFilterListener*
poListener);
/*!
*
\brief
Retire un écouteur de cette liste.
*
*
\param
poListener L'écouteur à retirer.
*/
void
RemoveListener(IBinaryFilterListener*
poListener);
protected
:
/*!
*
\brief
Notifie l'ajout d'une nouvelle propriété filtrée sur l'opérande de gauche.
*
*
\param
rstrLeftProperty Le nom de la propriété.
*/
void
NotifyLeftFilteredPropertyAdded(const
std::
string&
rstrLeftProperty);
/*!
*
\brief
Notifie le retrait d'une propriété anciennement filtrée sur l'opérande de gauche.
*
*
\param
rstrLeftProperty Le nom de la propriété.
*/
void
NotifyLeftFilteredPropertyRemoved(const
std::
string&
rstrLeftProperty);
/*!
*
\brief
Notifie l'ajout d'une nouvelle propriété filtrée sur l'opérande de droite.
*
*
\param
rstrRightProperty Le nom de la propriété.
*/
void
NotifyRightFilteredPropertyAdded(const
std::
string&
rstrRightProperty);
/*!
*
\brief
Notifie le retrait d'une propriété anciennement filtrée sur l'opérande de droite.
*
*
\param
rstrRightProperty Le nom de la propriété.
*/
void
NotifyRightFilteredPropertyRemoved(const
std::
string&
rstrRightProperty);
}
;
Je vous passe l'implémentation, qui est maintenant familière.
Encore une petite chose : Un filtre pourra être composé de plusieurs filtres. Par conséquent, pour ne pas perdre la chaîne des événements, il doit être lui aussi écouteur de ses filtres composants, pour qu'en cas de changement, transmettre le message.
class
IBinaryFilter : public
IBinaryFilterListener
{
protected
:
/*!
*
\brief
Une propriété de l'opérande de gauche du filtre est désormais ou à nouveau filtrée.
*
*
\param
[in] poFilter Le filtre à l'origine de cet évennement.
*
\param
[in] rstrLeftProperty Le nom de la propriété.
*/
virtual
void
OnBinaryFilteredLeftPropertyAdded(const
IBinaryFilter*
poFilter, const
std::
string&
rstrLeftProperty);
/*!
*
\brief
Une propriété de l'opérande de gauche n'est plus filtrée par un composant du filtre.
*
*
\param
[in] poFilter Le filtre à l'origine de cet évennement.
*
\param
[in] rstrLeftProperty Le nom de la propriété.
*/
virtual
void
OnBinaryFilteredLeftPropertyRemoved(const
IBinaryFilter*
poFilter, const
std::
string&
rstrLeftProperty);
/*!
*
\brief
Une propriété de l'opérande de droite du filtre est désormais ou à nouveau filtrée.
*
*
\param
[in] rstrRightProperty Le nom de la propriété.
*
\param
[in] poFilter Le filtre à l'origine de cet évennement.
*/
virtual
void
OnBinaryFilteredRightPropertyAdded(const
IBinaryFilter*
poFilter, const
std::
string&
rstrRightProperty);
/*!
*
\brief
Une propriété de l'opérande de droite n'est plus filtrée par un composant du filtre.
*
*
\param
[in] rstrRightProperty Le nom de la propriété.
*
\param
[in] poFilter Le filtre à l'origine de cet évennement.
*/
virtual
void
OnBinaryFilteredRightPropertyRemoved(const
IBinaryFilter*
poFilter, const
std::
string&
rstrRightProperty);
}
;
Ces callback ne feront que relayer l'info.
Concernant l'utilisateur, il sera alors possible de demander au filtre, à quelles propriétés il des données il doit écouter pour être informé et s'il est nécessaire de remettre en question sa validité. En effet, si nous avons relié un livre à son auteur, le filtre possèdera "BOOK::ID_AUTHOR <-> AUTHOR::ID" dans ses listes de propriétés impliqués, ce qui permettra, après réconciliation de notre pointeur de donnée, de suivre les changements des deux objets et être informé d'un besoin de cassure de la relation.
Revenons sur notre pointeur. Encore une fois, c'est inutile de stocker le filtre à l'échelle de l'objet. En effet, une relation n'appartient pas à une donnée mais à une structure. C'est pourquoi, nous stockerons le filtre au niveau du gestionnaire. Ce dernier possèdera une dictionnaire reliant un identifiant avec le filtre. L'identifiant sera de type entier (par gagner en performance par rapport aux chaînes) généré par une macro selon l'offset du pointeur dans la classe.
template
<
class
T>
class
CGesData : public
IGesData
{
public
:
/*!
*
\brief
Enregistre un filtre de pointeur de donnée intelligent.
*
*
\param
iPtrIdent L'identifiant.
*
\param
poFilter Le filtre.
*/
void
RegisterSmartDataPtrFilter(int
iPtrIdent, IBinaryFilter*
poFilter);
/*!
*
\brief
Obtient un filtre de pointeur de donnée intelligent par son identifiant.
*
*
\param
iPtrIdent L'identifiant.
*
\return
Le filtre.
*/
IBinaryFilter*
GetSmartDataPtrFilter(int
iPtrIdent) const
;
private
:
typedef
std::
map<
int
, IBinaryFilter*>
TMapSmartDataPtrFilters; /*!
< Type de mapping des filtres de pointeurs et leur identifiant.
*/
TMapSmartDataPtrFilters m_mapSmartDataPtrFilters; /*!
< Mapping des filtres de pointeurs et leur identifiant.
*/
}
;
Le constructeur du pointeur intelligent de donnée lui prendra en paramètre le "maître" (dans notre exemple le livre) ainsi que l'identifiant du filtre pour comparer avec les auteurs. Voyons également les autres méthodes
template
<
class
TMaster, class
TSlave>
class
CSmartDataPtr
{
public
:
/*!
*
\brief
Constructeur.
*
*
\param
pMaster Le "maître".
*
\param
iIdent L'identifiant du filtre à utiliser pour retrouve "l'esclave".
*/
CSmartDataPtr(TMaster*
pMaster, int
iIdent);
/*!
*
\brief
Opérateur *
*
*
\return
Le pointeur de la donnée pointée.
*/
TSlave&
operator
*
() const
;
/*!
*
\brief
Opérateur ->
*
*
\return
Le pointeur de la donnée pointée.
*/
TSlave*
operator
->
() const
;
/*!
*
\brief
Conversion implicite en TSlave*
*
*
\return
Le pointeur de la donnée pointée.
*/
operator
TSlave*
();
private
:
/*!
*
\brief
Copie interdite.
*/
CSmartDataPtr(const
CSmartDataPtr<
TMaster, TSlave>&
lst) {
}
/*!
*
\brief
Obtient un pointeur vers la donnée réconcilée.
*
*
\return
Un pointeur vers la donnée réconciliée ou 0 si elle n'a pas été trouvé.
*/
TSlave*
GetPtr();
CData<
TMaster>*
m_pMaster; /*!
< Le maître.
*/
CData<
TSlave>*
m_pSlave; /*!
< L'esclave.
*/
int
m_iIdent; /*!
< L'identifiant du filtre.
*/
}
;
Concernant l'implémentation, c'est très simple :
template
<
class
TMaster, class
TSlave>
CSmartDataPtr<
TMaster, TSlave>
::
CSmartDataPtr(TMaster*
pMaster, int
iIdent)
:
m_pMaster(pMaster), m_pSlave(0
), m_iIdent(iIdent)
{
// important : m_pSlave à 0
}
// Réconciliation si c'est pas déjà fait.
template
<
class
TMaster, class
TSlave>
TSlave*
CSmartDataPtr<
TMaster, TSlave>
::
GetPtr()
{
if
(!
m_pSlave &&
m_pMaster)
{
IBinaryFilter*
poFilter =
CGesManager::
GetInstance().GetSmartDataPtrFilter<
TMaster>
(m_iIdent);
CDataIterator<
TSlave>
it; CData<
TSlave>*
pCurrentData =
0
;
while
((pCurrentData =
it.GetData()) !=
0
)
{
if
(poFilter->
IsOk(m_pMaster, pCurrentData))
{
m_pSlave =
pCurrentData;
break
;
}
it.Next();
}
}
return
static_cast
<
TSlave*>
(m_pSlave);
}
// Accession
template
<
class
TMaster, class
TSlave>
TSlave&
CSmartDataPtr<
TMaster, TSlave>
::
operator
*
() const
{
return
GetPtr();
}
template
<
class
TMaster, class
TSlave>
TSlave*
CSmartDataPtr<
TMaster, TSlave>
::
operator
->
() const
{
return
GetPtr();
}
template
<
class
TMaster, class
TSlave>
CSmartDataPtr<
TMaster, TSlave>
::
operator
TSlave*
()
{
return
GetPtr();
}
Cela nous fait une base pour aller plus loin. Réfléchissons sur ce qui pourrait faire que la relation ne fonctionne plus (où ne peut plus être récréée).
Evennement | A écouter | Détails |
---|---|---|
Le maître a été supprimé des données | CGesData<TMaster> | Si le maître a été détruit, la réconciliation n'est plus possible |
L'esclave a été supprimé des données | CGesData<TSlave> | Si la réconciliation a été faite, et que l'esclave a été détruit, nous devons le réinitialiser sous peine de renvoyer un pointeur invalide au prochain appel |
Une propriété filtrée du maître a été modifié | CData<TMaster> sur les propriété "gauches" du filtre | Par exemple, si le "ID_AUTHOR" d'un livre change, l'auteur précédemment trouvé est obsolète. |
Une propriété filtrée de l'esclave a été modifié | CData<TSlave> sur les propriété "droite" du filtre | Par exemple, si le "ID" d'un auteur change, l'auteur précédemment trouvé est obsolète. |
Le filtre a été modifié (ajout/suppression de propriété filtré) | IBinaryFilter | Si le filtre a changé, la relation est alors à refaire. |
D'après le tableau, implémentons les interfaces nécessaires :
template
<
class
TMaster, class
TSlave>
class
CSmartDataPtr :
public
IGesDataListener<
TSlave>
,
public
IGesDataListener<
TMaster>
,
public
IDataPropertyListener<
TSlave>
,
public
IDataPropertyListener<
TMaster>
,
public
IBinaryFilterListener
{
public
:
/*!
*
\brief
Destructeur.
*/
~
CSmartDataPtr();
/*!
*
\brief
"Casse" la relation.
*/
void
Disconnect();
private
:
/*!
*
\brief
Une/plusieurs donnée(s) de type Maitre a(ont) été ajoutée(s), celà ne nous concerne pas.
*
*
\param
poData Pointeur vers les données.
*
\param
ulCount Le nombre de données ajoutées.
*/
virtual
void
OnDataAdded(CData<
TMaster>**
poData, unsigned
long
ulCount) {}
;
/*!
*
\brief
Une/plusieurs donnée(s) de type Maitre a(ont) été retirée(s), si le notre en fait parti, nous cassons la relation.
*
*
\param
poData Pointeur vers les données.
*
\param
ulCount Le nombre de données retirées.
*/
virtual
void
OnDataRemoved(CData<
TMaster>**
poData, unsigned
long
ulCount);
/*!
*
\brief
Le gestionnaire des maîtres a été détruit, nous cassons la relation.
*
*
\param
pGesData Le gestionnaire.
*/
virtual
void
OnGesDestroyed(CGesData<
TMaster>*
pGesData);
/*!
*
\brief
Une/plusieurs donnée(s) de type Esclave a(ont) été ajoutée(s), celà ne nous concerne pas.
*
*
\param
poData Pointeur vers les données.
*
\param
ulCount Le nombre de données ajoutées.
*/
virtual
void
OnDataAdded(CData<
TSlave>**
poData, unsigned
long
ulCount) {}
;
/*!
*
\brief
Une/plusieurs donnée(s) de type Esclave a(ont) été retirée(s), si le notre, en fait parti, nous cassons la relation.
*
*
\param
poData Pointeur vers les données.
*
\param
ulCount Le nombre de données retirées.
*/
virtual
void
OnDataRemoved(CData<
TSlave>**
poData, unsigned
long
ulCount);
/*!
*
\brief
Le gestionnaire des esclaves a été détruit, nous cassons la relation.
*
*
\param
pGesData Le gestionnaire.
*/
virtual
void
OnGesDestroyed(CGesData<
TSlave>*
pGesData);
/*!
*
\brief
Une propriété filtrée de l'esclave a été modifiée, nous cassons la relation.
*
*
\param
poData La donnée.
*
\param
poProperty La propriété.
*/
virtual
void
OnDataPropertyChanged(CData<
TSlave>*
poData, const
IProperty*
poProperty);
/*!
*
\brief
Une propriété filtrée du maître a été modifiée, nous cassons la relation.
*
*
\param
poData La donnée.
*
\param
poProperty La propriété.
*/
virtual
void
OnDataPropertyChanged(CData<
TMaster>*
poData, const
IProperty*
poProperty);
/*!
*
\brief
Le filtre a changé, la relation n'est plus à jour.
*
*
\param
poFilter Le filtre.
*
\param
rstrLeftProperty La propriété concerné.
*/
virtual
void
OnBinaryFilteredLeftPropertyAdded(const
IBinaryFilter*
poFilter, const
std::
string&
rstrLeftProperty) {}
/*!
*
\brief
Le filtre a changé, la relation n'est plus à jour.
*
*
\param
poFilter Le filtre.
*
\param
rstrLeftProperty La propriété concerné.
*/
virtual
void
OnBinaryFilteredLeftPropertyRemoved(const
IBinaryFilter*
poFilter, const
std::
string&
rstrLeftProperty) {}
/*!
*
\brief
Le filtre a changé, la relation n'est plus à jour.
*
*
\param
poFilter Le filtre.
*
\param
rstrLeftProperty La propriété concerné.
*/
virtual
void
OnBinaryFilteredRightPropertyAdded(const
IBinaryFilter*
poFilter, const
std::
string&
rstrRightProperty) {}
/*!
*
\brief
Le filtre a changé, la relation n'est plus à jour.
*
*
\param
poFilter Le filtre.
*
\param
rstrLeftProperty La propriété concerné.
*/
virtual
void
OnBinaryFilteredRightPropertyRemoved(const
IBinaryFilter*
poFilter, const
std::
string&
rstrRightProperty) {}
/*!
*
\brief
Démarre/stop l'écoute de l'esclave.
*
*
\param
bListen true pour démarrer l'écoute, false pour l'arrêter.
*/
void
ListenSlaveProperties(bool
bListen);
/*!
*
\brief
Démarre/stop l'écoute du maître.
*
*
\param
bListen true pour démarrer l'écoute, false pour l'arrêter.
*/
void
ListenMasterProperties(bool
bListen);
/*!
*
\brief
Démarre/stop l'écoute du gestionnaire des maîtres.
*
*
\param
bListen true pour démarrer l'écoute, false pour l'arrêter.
*/
void
ListenGesMasterData(bool
bListen);
/*!
*
\brief
Démarre/stop l'écoute du gestionnaire des esclaves.
*
*
\param
bListen true pour démarrer l'écoute, false pour l'arrêter.
*/
void
ListenGesSlaveData(bool
bListen);
/*!
*
\brief
Démarre/stop l'écoute du filtre.
*
*
\param
bListen true pour démarrer l'écoute, false pour l'arrêter.
*/
void
ListenFilter(bool
bListen);
/*!
*
\brief
Démarre/stop l'écoute de tout ce dont il est nécessaire.
*
*
\param
bListen true pour démarrer l'écoute, false pour l'arrêter.
*/
void
ListenAll(bool
bListen);
}
;
Et à chaque notification, la relation est cassée, c'est à dire que le "m_pSlave" est remis à 0, nous arrêtons toutes les écoutes, et la recherche d'une relation sera effectuée au prochain appel de GetPtr().
Le reste n'est qu'algorithmie, vous le trouverez dans les sources ci-jointes, j'ai également rajouté un système d'écoute au cas où notre utilisateur aurai besoin d'être notifié d'une réconciliation ou d'une cassure de relation afin de mettre à jour son IHM.
Pour le filtre, nous allons faire une macro positionnant un filtre d'égalité (le plus courant), et si nécessaire nous en ferons une pour positionner un filtre personnalisé.
#define BEGIN_SMART_DATA_PTR(Class) \
template<> void SSmartDataPtrRegistrer
<Class>
::RegisterSmartDataPtr() {
#define REG_SMART_DATA_PTR(MasterType, MasterPtr, MasterProp, SlaveProp) \
GesMgr.RegisterSmartDataPtrFilter
<MasterType>
(offsetof(MasterType,MasterPtr), new CBinaryEqualFilter(MasterProp, SlaveProp));
#define END_SMART_DATA_PTR() }
Ainsi qu'une macro d'initialisation du filtre
#define InitSmartPtr(MasterType, MasterPtr) MasterPtr(this, offsetof(MasterType, MasterPtr))
Puis l'ajout à l'initialiseur de donnée.
#define MAKE_DATA(Class) \
friend class CData
<Class>
; \
friend class IIoConnector
<Class>
; \
friend struct SPropertyRegistrer
<Class>
; \
friend struct SIdentRegistrer
<Class>
; \
friend struct SSmartDataPtrRegistrer
<Class>
;
Voyons ce que cela donne au niveau de la configuration des données métier (notre livre référençant son auteur)
class
CAuthor;
class
CBook : public
CData<
CBook>
{
MAKE_DATA(CBook)
public
:
CAuthor*
GetAuthor();
void
SetAuthor(CAuthor*
pAuthor);
private
:
CSmartDataPtr<
CBook, CAuthor>
m_pAuthor;
TUInt64Property m_ulIdentAuthor;
}
;
BEGIN_SMART_DATA_PTR(CBook)
REG_SMART_DATA_PTR(CBook, m_pAuthor, "ID_AUTHOR"
, "ID"
)
END_SMART_DATA_PTR()
CBook::
CBook()
:
InitSmartPtr(CBook, m_pAuthor)
{
}
CAuthor*
CBook::
GetAuthor()
{
return
m_pAuthor;
}
void
CBook::
SetAuthor(CAuthor*
pAuthor)
{
m_ulIdentAuthor =
pAuthor->
GetIdent();
}
Nous proposons ainsi un code simple et léger à l'utilisateur, tout en conservant la cohérence des données. Objectif atteint !
V-B. Les relations 1-n▲
Gérons maintenant les relations 1-n, c'est à dire des listes de données intelligente. En fait, ces listes vont fonctionner de la même manière que les pointeurs décrit ci-dessus, sauf qu'elles n'encapsulerons pas une seule donnée réconciliée, mais plusieurs.
Comme pour la gestion des relation 1-1, la liste possèdera un identifiant sous forme d'entier, et le filtre lui sera stocké dans le gestionnaire.
Voyons les objectifs, et ensuite le code dans son ensemble.
- Se connecter/déconnecter sur demande, via les accesseurs (GetCount et GetAt)
- Se déconnecter sur demande direct, dans le cas d'utilisation ponctuelle
- Se tenir à jour selon les fluctuations des données
- Notifier de son comportement et ses changements
Concernant l'algorithmie de réconciliation, nous la verrons dans le code, en revanche posons les données à écouter pour assurer la cohérence, une fois la réconciliation faîte :
Evennement | A écouter | Détails |
---|---|---|
Le maître a été supprimé des données | CGesData<TMaster> | Déconnection, les réconciliations futurs ne sont plus possibles |
Un/plusieurs esclave(s) a(ont) été supprimé(s) des données | CGesData<TSlave> | Retrait des éventuels esclaves supprimés qui ont été précédemment réconciliés |
>Un/plusieurs esclave(s) a(ont) été ajouté(s) aux données | CGesData<TSlave> | Si la réconciliation a été faite, ajout des esclaves qui passent le filtre dans la liste |
Une propriété filtrée du maître a été modifié | CData<TMaster> sur les propriété "gauches" du filtre | La réconciliation est obsolète, nous réconcilions tout. |
Une propriété filtrée d'un esclave a été modifié | CData<TSlave> sur les propriété "droite" du filtre | Retrait de cet élément s'il passe plus le filtre. |
Le filtre a été modifié (ajout/suppression de propriété filtré) | IBinaryFilter | La réconciliation est obsolète, nous réconcilions tout. |
Voyons maintenant le code produit
/*!
*
\brief
Liste de données intelligente.
*/
template
<
class
TMaster, class
TSlave>
class
CSmartDataList
:
public
IGesDataListener<
TSlave>
,
public
IGesDataListener<
TMaster>
,
public
IDataPropertyListener<
TSlave>
,
public
IDataPropertyListener<
TMaster>
,
public
IBinaryFilterListener
{
public
:
/*!
*
\brief
Constructeur.
*
*
\param
pMaster Le Maître
*
\param
iPtrIdent L'identifiant.
*/
CSmartDataList(CData<
TMaster>*
pMaster, int
iPtrIdent);
/*!
*
\brief
Déconnecte l aliste aux données.
*/
void
Disconnect();
/*!
*
\brief
Ajoute un écouteur à cette liste.
*
*
\param
poListener L'écouteur à ajouter.
*/
void
AddListener(ISmartDataListListener<
TMaster, TSlave>*
poListener);
/*!
*
\brief
Retire un écouteur de la liste.
*
*
\param
poListener L'écouteur à retirer.
*/
void
RemoveListener(ISmartDataListListener<
TMaster, TSlave>*
poListener);
/*!
*
\brief
Obtient le nombre d'élément contenus dans cette liste.
*
*
\return
Le nombre d'élément contenus dans cette liste.
*/
unsigned
long
GetCount();
/*!
*
\brief
Obtient un élément par son index.
*
*
\param
ulIndex L'index.
*
\return
L'élement correspondant.
*/
TSlave*
GetAt(unsigned
long
ulIndex);
private
:
/*!
*
\brief
Copie interdite.
*/
CSmartDataList(const
CSmartDataList<
TMaster, TSlave>&
lst) {
}
/*!
*
\brief
Connect la liste aux données.
*/
void
Connect();
/*!
*
\brief
Réconcilie la liste avec une ou toutes les données.
*
*
\param
pSlave Une donnée particulière ou 0 pour tout réconcilier.
*/
void
Reconcil(CData<
TSlave>*
pSlave =
0
);
/*!
*
\brief
Une/plusieurs donnée(s) de type Maitre a(ont) été ajoutée(s), celà ne nous concerne pas.
*
*
\param
poData Pointeur vers les données.
*
\param
ulCount Le nombre de données ajoutées.
*/
virtual
void
OnDataAdded(CData<
TMaster>**
poData, unsigned
long
ulCount){}
;
/*!
*
\brief
Une/plusieurs donnée(s) de type Maitre a(ont) été retirée(s), si le notre en fait parti, nous cassons la relation.
*
*
\param
poData Pointeur vers les données.
*
\param
ulCount Le nombre de données retirées.
*/
virtual
void
OnDataRemoved(CData<
TMaster>**
poData, unsigned
long
ulCount);
/*!
*
\brief
Le gestionnaire des maîtres a été détruit, nous cassons la relation.
*
*
\param
pGesData Le gestionnaire.
*/
virtual
void
OnGesDestroyed(CGesData<
TMaster>*
pGesData);
/*!
*
\brief
Une/plusieurs donnée(s) de type Esclave a(ont) été ajoutée(s), celà ne nous concerne pas.
*
*
\param
poData Pointeur vers les données.
*
\param
ulCount Le nombre de données ajoutées.
*/
virtual
void
OnDataAdded(CData<
TSlave>**
poData, unsigned
long
ulCount);
/*!
*
\brief
Une/plusieurs donnée(s) de type Esclave a(ont) été retirée(s), si le notre, réconcilié, en fait parti, nous cassons la relation.
*
*
\param
poData Pointeur vers les données.
*
\param
ulCount Le nombre de données retirées.
*/
virtual
void
OnDataRemoved(CData<
TSlave>**
poData, unsigned
long
ulCount);
/*!
*
\brief
Le gestionnaire des esclaves a été détruit, nous cassons la relation.
*
*
\param
pGesData Le gestionnaire.
*/
virtual
void
OnGesDestroyed(CGesData<
TSlave>*
pGesData);
/*!
*
\brief
Une propriété filtrée d'un esclave a été modifiée, nous cassons la relation.
*
*
\param
poData La donnée.
*
\param
poProperty La propriété.
*/
virtual
void
OnDataPropertyChanged(CData<
TSlave>*
poData, const
IProperty*
poProperty);
/*!
*
\brief
Une propriété filtrée d'un maître a été modifiée, nous cassons la relation.
*
*
\param
poData La donnée.
*
\param
poProperty La propriété.
*/
virtual
void
OnDataPropertyChanged(CData<
TMaster>*
poData, const
IProperty*
poProperty);
/*!
*
\brief
Le filtre a changé, la relation n'est plus à jour.
*
*
\param
poFilter Le filtre.
*
\param
rstrLeftProperty La propriété concerné.
*/
virtual
void
OnBinaryFilteredLeftPropertyAdded(const
IBinaryFilter*
poFilter, const
std::
string&
rstrLeftProperty) {}
/*!
*
\brief
Le filtre a changé, la relation n'est plus à jour.
*
*
\param
poFilter Le filtre.
*
\param
rstrLeftProperty La propriété concerné.
*/
virtual
void
OnBinaryFilteredLeftPropertyRemoved(const
IBinaryFilter*
poFilter, const
std::
string&
rstrLeftProperty) {}
/*!
*
\brief
Le filtre a changé, la relation n'est plus à jour.
*
*
\param
poFilter Le filtre.
*
\param
rstrLeftProperty La propriété concerné.
*/
virtual
void
OnBinaryFilteredRightPropertyAdded(const
IBinaryFilter*
poFilter, const
std::
string&
rstrRightProperty) {}
/*!
*
\brief
Le filtre a changé, la relation n'est plus à jour.
*
*
\param
poFilter Le filtre.
*
\param
rstrLeftProperty La propriété concerné.
*/
virtual
void
OnBinaryFilteredRightPropertyRemoved(const
IBinaryFilter*
poFilter, const
std::
string&
rstrRightProperty) {}
/*!
*
\brief
Notifie de l'ajout d'éléments dans cette liste.
*
*
\param
pSlave Pointeur sur un tableau d'éléments.
*
\param
ulCount Nombre d'élément ajoutés.
*/
void
NotifyElementAdded(CData<
TSlave>**
pSlave, unsigned
long
ulCount);
/*!
*
\brief
Notifie du retrait d'éléments dans cette liste.
*
*
\param
pSlave Pointeur sur un tableau d'éléments.
*
\param
ulCount Nombre d'élément ajoutés.
*/
void
NotifyElementRemoved(CData<
TSlave>**
pSlave, unsigned
long
ulCount);
/*!
*
\brief
Notifie de la connexion de cette liste.
*/
void
NotifyConnected();
/*!
*
\brief
Notifie de la déconnexion de cette liste.
*/
void
NotifyDisconnected();
/*!
*
\brief
Démarre/stop l'ecoute les propriétés filtrés d'un ou plusieurs esclaves.
*
*
\param
bListen true pour démarrer l'écoute, sinon false.
*
\param
pSlave Un esclave sur lequel agir sinon 0 pour agir sur tous les esclaves réconciliés.
*/
void
ListenSlaveProperties(bool
bListen, CData<
TSlave>*
pSlave =
0
);
/*!
*
\brief
Démarre/stop l'ecoute les propriétés filtrés du maître.
*
*
\param
bListen true pour démarrer l'écoute, sinon false.
*/
void
ListenMasterProperties(bool
bListen);
CData<
TMaster>*
m_pMaster; /*!
< Le maître.
*/
int
m_iPtrIdent; /*!
< L'identifiant.
*/
bool
m_bConnected; /*!
< L'état de connexion avec les données.
*/
typedef
std::
vector<
CData<
TSlave>*>
TVecSlave; /*!
< Type de stockage des esclaves réconciliés.
*/
TVecSlave m_vecSlaves; /*!
< Les esclaves réconciliés.
*/
}
;
// Constructeur
template
<
class
TMaster, class
TSlave>
CSmartDataList<
TMaster, TSlave>
::
CSmartDataList(CData<
TMaster>*
pMaster, int
iPtrIdent)
:
m_pMaster(pMaster), m_iPtrIdent(iPtrIdent), m_bConnected(false
)
{
}
// Connection
template
<
class
TMaster, class
TSlave>
void
CSmartDataList<
TMaster, TSlave>
::
Connect()
{
// Si c'est une erreur et que nous sommes sommes déjà connecté
if
(m_bConnected)
return
;
// Nous récupérons le filtre
IBinaryFilter*
poFilter =
CGesManager::
GetInstance().GetSmartDataListFilter<
TMaster>
(m_iPtrIdent);
// Puis nous parcourrons les esclaves
CDataIterator<
TSlave>
it; CData<
TSlave>*
pCurrentData =
0
;
while
((pCurrentData =
it.GetData()) !=
0
)
{
// Pour chaque esclave, s'il passe le filtre
if
(poFilter->
IsOk(m_pMaster, pCurrentData))
m_vecSlaves.push_back(pCurrentData); // Nous l'ajoutons à la liste
ListenSlaveProperties(true
, pCurrentData); // Dans tous les cas il faut l'écouter
it.Next();
}
// Nous nous branchons aux gestionnaires
CGesManager::
GetInstance().AddListener<
TMaster>
(this
);
CGesManager::
GetInstance().AddListener<
TSlave>
(this
);
// Nous écoutons les propriétés du maître
ListenMasterProperties(true
);
// Puis le filtre
poFilter->
AddListener(this
);
m_bConnected =
true
;
NotifyConnected();
}
// Réconciliation
template
<
class
TMaster, class
TSlave>
void
CSmartDataList<
TMaster, TSlave>
::
Reconcil(CData<
TSlave>*
pSlave)
{
TVecSlave vecAdded;
TVecSlave vecRemoved;
// Nous récupérons le filtre
IBinaryFilter*
poFilter =
CGesManager::
GetInstance().GetSmartDataListFilter<
TMaster>
(m_iPtrIdent);
// Si un esclave particulier a été spécifié
if
(pSlave)
{
// Nous regardons s'il fait parti de notre liste
TVecSlave::
iterator it =
std::
find(m_vecSlaves.begin(), m_vecSlaves.end(), pSlave);
// S'il n'en fait pas parti, nous regardons si après ce changement il peut l'intégrer
if
(it ==
m_vecSlaves.end())
{
if
(poFilter->
IsOk(m_pMaster, pSlave))
{
m_vecSlaves.push_back(pSlave); // Si c'est le cas, nous l'ajoutons.
vecAdded.push_back(pSlave);
}
}
else
// S'il fait parti de la liste, nous checkons si sa présence est toujours valable
{
if
(!
poFilter->
IsOk(m_pMaster, pSlave)) /
: Si ce n'est pas le cas
{
m_vecSlaves.erase(it); // Nous le retirons
vecRemoved.push_back(pSlave);
}
}
}
else
// S'il faut tout réconcilier, nous parcourons tout et réconcilions chaque donnée
{
CDataIterator<
TSlave>
itData; CData<
TSlave>*
pCurrentData =
0
;
while
((pCurrentData =
itData.GetData()) !=
0
)
{
Reconcil(pCurrentData);
itData.Next();
}
}
// Enfin nous nous approprions le résultat de la réconciliation
TVecSlave::
iterator it =
vecRemoved.begin();
while
(it !=
vecRemoved.end())
{
m_vecSlaves.erase(std::
remove(m_vecSlaves.begin(), m_vecSlaves.end(), (*
it)), m_vecSlaves.end());
++
it;
}
std::
copy(vecAdded.begin(), vecAdded.end(), std::
back_inserter(m_vecSlaves));
// Puis nous notifions
if
(vecRemoved.size() >
0
)
NotifyElementRemoved(&
vecRemoved[0
], static_cast
<
unsigned
long
>
(vecRemoved.size()));
if
(vecAdded.size() >
0
)
NotifyElementAdded(&
vecAdded[0
], static_cast
<
unsigned
long
>
(vecAdded.size()));
}
// Déconnection
template
<
class
TMaster, class
TSlave>
void
CSmartDataList<
TMaster, TSlave>
::
Disconnect()
{
// Nous nous débranchons de chaque donnée esclave
CDataIterator<
TSlave>
it; CData<
TSlave>*
pCurrentData =
0
;
while
((pCurrentData =
it.GetData()) !=
0
)
{
ListenSlaveProperties(false
, pCurrentData);
it.Next();
}
// Puis des gestionnaires
CGesManager::
GetInstance().RemoveListener<
TMaster>
(this
);
CGesManager::
GetInstance().RemoveListener<
TSlave>
(this
);
// Puis du maître
ListenMasterProperties(false
);
// Puis du filtre
IBinaryFilter*
poFilter =
CGesManager::
GetInstance().GetSmartDataListFilter<
TMaster>
(m_iPtrIdent);
poFilter->
AddListener(false
);
// Nous effaçons les données internes
m_vecSlaves.clear();
m_bConnected =
false
;
// Nous notifions.
NotifyDisconnected();
}
// Renvoi le nombre d'élément réconciliés, donc la taille du conteneur interne.
template
<
class
TMaster, class
TSlave>
unsigned
long
CSmartDataList<
TMaster, TSlave>
::
GetCount()
{
Connect();
return
static_cast
<
unsigned
long
>
(m_vecSlaves.size());
}
// Renvoi un élément réconcilié, donc l'élément x du conteneur interne.
template
<
class
TMaster, class
TSlave>
TSlave*
CSmartDataList<
TMaster, TSlave>
::
GetAt(unsigned
long
ulIndex)
{
return
static_cast
<
TSlave*>
(m_vecSlaves[ulIndex]);
}
// Démarre/stop les propriétés d'un esclave si spécifié sinon de tousles esclaves
template
<
class
TMaster, class
TSlave>
void
CSmartDataList<
TMaster, TSlave>
::
ListenSlaveProperties(bool
bListen, CData<
TSlave>*
pSlave)
{
// Nous récupérons le filtre
IBinaryFilter*
poFilter =
CGesManager::
GetInstance().GetSmartDataListFilter<
TMaster>
(m_iPtrIdent);
// Si une donnée est spécifié, nous démarrons/stoppons l'écoute selon le booléen
if
(pSlave)
{
CBinaryFilterPropertiesIterator it(poFilter, CBinaryFilterPropertiesIterator::
E_RIGHT);
while
(it.HasNext())
{
if
(bListen)
pSlave->
AddPropertyListener(this
, it.GetProperty());
else
pSlave->
RemovePropertyListener(this
, it.GetProperty());
it.Next();
}
}
else
{
// Sinon nous parcourons tout et nous démarrerons/stoppons l'écoute sur chaque donnée
CDataIterator<
TSlave>
itData; CData<
TSlave>*
pCurrentData =
0
;
while
((pCurrentData =
itData.GetData()) !=
0
)
{
ListenSlaveProperties(bListen, pCurrentData);
itData.Next();
}
}
}
// Ecoute des propriétés filtrées du maître
template
<
class
TMaster, class
TSlave>
void
CSmartDataList<
TMaster, TSlave>
::
ListenMasterProperties(bool
bListen)
{
IBinaryFilter*
poFilter =
CGesManager::
GetInstance().GetSmartDataListFilter<
TMaster>
(m_iPtrIdent);
CBinaryFilterPropertiesIterator it(poFilter, CBinaryFilterPropertiesIterator::
E_LEFT);
while
(it.HasNext())
{
if
(bListen)
m_pMaster->
AddPropertyListener(this
, it.GetProperty());
else
m_pMaster->
RemovePropertyListener(this
, it.GetProperty());
it.Next();
}
}
// Le maître a été retiré, nous nous déconnectons
template
<
class
TMaster, class
TSlave>
void
CSmartDataList<
TMaster, TSlave>
::
OnDataRemoved(CData<
TMaster>**
pMaster, unsigned
long
ulCount)
{
// Un/plusieurs maitre a été retiré, vérifions que le notre soit toujours là
IBinaryFilter*
poFilter =
CGesManager::
GetInstance().GetSmartDataListFilter<
TMaster>
(m_iPtrIdent);
for
(unsigned
long
ulIt =
0
; ulIt <
ulCount; ulIt++
)
{
if
(pMaster[ulIt] ==
m_pMaster)
{
// Si c'est le cas, nous nous déconnectons
Disconnect();
m_pMaster =
0
;
break
;
}
}
}
// Le gestionnaire du maître se détruit, nous nous déconnectons.
template
<
class
TMaster, class
TSlave>
void
CSmartDataList<
TMaster, TSlave>
::
OnGesDestroyed(CGesData<
TMaster>*
pGesData)
{
Disconnect();
m_pMaster =
0
;
}
// Le gestionnaire des esclave se détruit, nous nous déconnectons.
template
<
class
TMaster, class
TSlave>
void
CSmartDataList<
TMaster, TSlave>
::
OnGesDestroyed(CGesData<
TSlave>*
pGesData)
{
Disconnect();
m_pMaster =
0
;
}
// Un ou plusieurs esclave(s) a(ont) été ajoutés, nous les checkons un part un voir s'il peuvent faire parti de cette liste.
template
<
class
TMaster, class
TSlave>
void
CSmartDataList<
TMaster, TSlave>
::
OnDataAdded(CData<
TSlave>**
pSlave, unsigned
long
ulCount)
{
TVecSlave vecAdded;
// Un/plusieurs esclave a été ajouté, est-ce qu'il a sa place dans cette liste ?
IBinaryFilter*
poFilter =
CGesManager::
GetInstance().GetSmartDataListFilter<
TMaster>
(m_iPtrIdent);
for
(unsigned
long
ulIt =
0
; ulIt <
ulCount; ulIt++
)
{
CData<
TSlave>*
pCurrent =
pSlave[ulIt];
if
(poFilter->
IsOk(m_pMaster, pCurrent))
{
m_vecSlaves.push_back(pCurrent);
vecAdded.push_back(pCurrent);
}
ListenSlaveProperties(true
, pCurrent);
}
if
(vecAdded.size() >
0
)
NotifyElementAdded(&
vecAdded[0
], static_cast
<
unsigned
long
>
(vecAdded.size()));
}
// Un ou plusieurs esclave(s) a(ont) été retiré(s), nous les retirons de la liste.
template
<
class
TMaster, class
TSlave>
void
CSmartDataList<
TMaster, TSlave>
::
OnDataRemoved(CData<
TSlave>**
pSlave, unsigned
long
ulCount)
{
TVecSlave vecRemoved;
// Un/plusieurs esclaves ont été retirée, nettyons notre liste
for
(unsigned
long
ulIt =
0
; ulIt <
ulCount; ulIt++
)
{
CData<
TSlave>*
pCurrent =
pSlave[ulIt];
TVecSlave::
iterator it =
std::
find(m_vecSlaves.begin(), m_vecSlaves.end(), pCurrent);
if
(it !=
m_vecSlaves.end())
{
vecRemoved.push_back(pCurrent);
m_vecSlaves.erase(it);
}
ListenSlaveProperties(false
, pSlave[ulIt]);
}
if
(vecRemoved.size() >
0
)
NotifyElementRemoved(&
vecRemoved[0
], static_cast
<
unsigned
long
>
(vecRemoved.size()));
}
// Une propriété filtré d'un esclave a été modifiée, nous réconcilions.
template
<
class
TMaster, class
TSlave>
void
CSmartDataList<
TMaster, TSlave>
::
OnDataPropertyChanged(CData<
TSlave>*
poData, const
IProperty*
poProperty)
{
// Une propriété d'un esclave a changé, il faut le recontrôler
Reconcil(poData);
}
// Une propriété filtré du maître a été modifié, nous réconcilions.
template
<
class
TMaster, class
TSlave>
void
CSmartDataList<
TMaster, TSlave>
::
OnDataPropertyChanged(CData<
TMaster>*
poData, const
IProperty*
poProperty)
{
// Une propriété du maître a changé, il faut le recontrôler
Reconcil();
}
template
<
class
TMaster, class
TSlave>
void
CSmartDataList<
TMaster, TSlave>
::
AddListener(ISmartDataListListener<
TMaster, TSlave>*
poListener)
{
m_vecListeners.push_back(poListener);
}
template
<
class
TMaster, class
TSlave>
void
CSmartDataList<
TMaster, TSlave>
::
RemoveListener(ISmartDataListListener<
TMaster, TSlave>*
poListener)
{
m_vecListeners.erase(std::
remove(m_vecListeners.begin(), m_vecListeners.end(), poListener), m_vecListeners.end());
}
template
<
class
TMaster, class
TSlave>
void
CSmartDataList<
TMaster, TSlave>
::
NotifyElementAdded(CData<
TSlave>**
pSlave, unsigned
long
ulCount)
{
TVecListeners::
iterator it =
m_vecListeners.begin();
while
(it !=
m_vecListeners.end())
{
(*
it)->
OnSmartDataListElementAdded(this
, pSlave, ulCount);
++
it;
}
}
template
<
class
TMaster, class
TSlave>
void
CSmartDataList<
TMaster, TSlave>
::
NotifyElementRemoved(CData<
TSlave>**
pSlave, unsigned
long
ulCount)
{
TVecListeners::
iterator it =
m_vecListeners.begin();
while
(it !=
m_vecListeners.end())
{
(*
it)->
OnSmartDataListElementRemoved(this
, pSlave, ulCount);
++
it;
}
}
template
<
class
TMaster, class
TSlave>
void
CSmartDataList<
TMaster, TSlave>
::
NotifyConnected()
{
TVecListeners::
iterator it =
m_vecListeners.begin();
while
(it !=
m_vecListeners.end())
{
(*
it)->
OnSmartDataListConnected(this
);
++
it;
}
}
template
<
class
TMaster, class
TSlave>
void
CSmartDataList<
TMaster, TSlave>
::
NotifyDisconnected()
{
TVecListeners::
iterator it =
m_vecListeners.begin();
while
(it !=
m_vecListeners.end())
{
(*
it)->
OnSmartDataListDisconnected(this
);
++
it;
}
}