Gérer ses données en C++


précédentsommairesuivant

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 :

 
Sélectionnez
// 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 :

 
Sélectionnez
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 :

Image non disponible

Ou via un intermédiaire :

Image non disponible

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 :

 
Sélectionnez
/*!
 * \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.

 
Sélectionnez

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 :

 
Sélectionnez
/*!
 * \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

 
Sélectionnez
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.

 
Sélectionnez
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.

 
Sélectionnez
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

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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é.

 
Sélectionnez
#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

 
Sélectionnez
#define InitSmartPtr(MasterType, MasterPtr) MasterPtr(this, offsetof(MasterType, MasterPtr))

Puis l'ajout à l'initialiseur de donnée.

 
Sélectionnez
#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)

 
Sélectionnez
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

 
Sélectionnez
/*!
 * \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 
	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;
	}
}

précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2009 Aurélien FILEZ Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.