Présentation des principaux design patterns en C++


précédentsommairesuivant

II. Prototype

Définition : un prototype est une classe dont le but est d'être clonée.

II-1. Exemple de besoin du protoype

Pendant la création de vos programmes, vous serez sans doute amenés à instancier des objets de taille conséquente en mémoire.
Créer un gros objet ne pose guère de problèmes. Mais en créer plusieurs à la suite peut amener à tuer les performances de votre application.
La solution est alors de copier l'objet de base, le prototype, puis de modifier ce qui doit l'être pour que le nouvel objet réponde aux besoins.

une autre utilisation est la possibilité qu'offre le clonage pour dubliquer des objets polymorphes. En effet pour utiliser le constructeur de copie, il faut connaître le type réel de l'objet, ce qui n'est plus le cas dès qu'on utilise le polymorphisme de dynamique en C++.

De cette façon, on s'aperçoit que le pattern prototype dispose d'une méthode obligatoire, Clone, et d'une facultative Change. La première a pour but d'effectuer une copie du prototype tandis que la seconde a pour rôle de changer l'état de l'objet.
Ainsi, le diagramme UML qui modélise le pattern prototype est :

Diagramme UML du DP prototype
Diagramme UML du pattern prototype

II-2. Exemple naïf d'implémentation

D'après l'analyse précédente et en prenant comme exemple une connexion Mysql, une première implémentation de ce pattern pourrait être tout simplement :

 
Sélectionnez

//fichier prototype.h

#ifndef PROTOTYPE_H
#define PROTOTYPE_H

#include <string>

class Prototype
{
        public:
        virtual ~Prototype();        
        virtual Prototype* Clone() const  = 0;
};

class MySQLManager: public Prototype
{
std::string m_host,m_login,m_pass;
std::string m_base,m_table;
public:
         MySQLManager(const std::string& host="",const std::string& login="",const std::string& pass="");
         MySQLManager* Clone() const;

         void Afficher();
         void Set(const std::string& base="",const std::string& table=""); 
};

#endif

Et dans le fichier prototype.cpp

 
Sélectionnez

#include <iostream>
#include "prototype.h"

Prototype::~Prototype()
{

}

MySQLManager::MySQLManager(const std::string& host,const std::string& login,const std::string& pass):
m_host(host),m_login(login),m_pass(pass)
{
//on se connecte à la base Mysql.
//ceci prend du temps car il faut se connecter puis s'identifier.
//Lourd.
}

MySQLManager* MySQLManager::Clone() const 
{
       //le constructeur de recopie fait tout le travail à notre place.
        return (new MySQLManager(*this));
} 

void MySQLManager::Set(const std::string& base,const std::string& table)
{
m_base=base;
m_table=table;
//on se connecte a la base m_base pour étudier m_table 
}
void MySQLManager::Afficher() const
{
        std::cout<<m_host<<"|"<<m_login<<"|"<<m_host<<"|"<<m_pass<<"|"<<m_base<<"|"<<m_table<<std::endl;
}

int main(void)
{
        MySQLManager manager1("localhost","Davidbrcz","motdepasse");
        manager1.Set("faussebase","table1");
        //ici manager1 est connecté sur localhost avec l'identifiant Davidbrcz sur la table table1 de faussebasse
        
        manager1.Afficher();

        //Maintenant, on doit travailler en parallèle sur table2 de faussebase2 toujours sur localhost.        
        //on crée donc un autre manager qu'on recupère avec un std::auto_ptr pour éviter de gérer la mémoire en cas de problème.
        std::auto_ptr<MySQLManager> manager2(manager1.Clone());
        //et ici manager2 est déjà connecté à localhost avec l'identifiant Davidbrcz.Pas besoin de se réidentifier.
        manager2->Afficher();        
        manager2->Set("faussebase2","table2");
        manager2->Afficher();

        //on travaile ..        

        return 0;
}

Ce code mérite quelques explications.
Tout d'abord on déclare une classe Prototype qui est une interface grâce à la méthode virtuelle pure Clone. Ensuite on déclare une classe MySQLManager qui hérite de Prototype.

On rend cette classe instanciable en définissant la méthode Clone. Mais la fonction virtuelle Prototype::Clone renvoie un Prototype. Or le type de retour ne rentre normalement pas dans la définition d'une fonction. Le compilateur devrait donc en théorie me crier dessus.

Mais si ceci compile sans problème c'est grâce aux valeurs de retour covariantes.
Pour faire simple les valeurs de retour covariantes sont les cas où la valeur de retour d'une fonction virtuelle est une référence ou un pointeur sur une classe C.Ainsi les classes qui redéfiniront cette fonction virtuelle pourront renvoyer un pointeur ou une référence sur une classe dérivée de C.

C'est ce qu'il se passe ici. MySQLManager dérive de Prototype donc MySQLManager::Clone peut renvoyer un pointeur vers une instance de MySQLManager.
Mais le problème ici, c'est que Clone renvoie un pointeur brut, utiliser un pointeur intelligent pour le gérer n'est pas du tout obligatoire. Si l'utilisateur utilise un pointeur brut pour gérer la mémoire, ceci peut poser des problèmes pour récupérer la mémoire en cas d'exception.

II-3. Implémentation avec les templates

Les retours covariants ne marchent pas avec des pointeurs intelligents. Pour contourner cela, on doit utiliser des templates
Ainsi les classes Prototype et MySQLManager deviennent :

 
Sélectionnez

//fichier prototypetpl.h

#ifndef PROTOTYPE_H
#define PROTOTYPE_H

#include <string>
#include <memory>

template <class T> class Prototype
{
public:
    virtual ~Prototype(){}        
    virtual std::auto_ptr<T> Clone() const =0 ;
};

class MySQLManager: public Prototype<MySQLManager>
{

    std::string m_host,m_login,m_pass;
    std::string m_base,m_table;

public:
    MySQLManager(const std::string host="",const std::string login="",const std::string pass="");        
        
    void Afficher() const ;        
    std::auto_ptr<MySQLManager> Clone() const ;
    void Set(const std::string base="",const std::string table=""); 
};

#endif

Et le fichier prototype.cpp (seul change Clone, le reste est identique)

 
Sélectionnez

std::auto_ptr<MySQLManager> MySQLManager::Clone() const 
{
    return std::auto_ptr<MySQLManager>(new MySQLManager(*this));
} 

int main(void)
{
//main ne change pas, on est juste sûr que la mémoire sera libérée en cas de problème
}

L'utilisation des templates permet de simuler des retours covariants avec les pointeurs intelligents. Ceci assure une plus grande résistance au programme.


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 © 2007 Côme David. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.