Projet

Général

Profil

AbulEduSingletonV1

Introduction

Le singleton est un patron de conception qui a pour mission d'instancier qu' une seule fois un objet.
Aussi, l'objet dans le programme a une instance unique et ne se recréé pas à chaque nouvel appel du constructeur.
A la place, un pointeur sur l'instance en cours est renvoyé.

A chaque appel du constructeur, c'est le singleton qui contrôle l'instanciation de l'objet.
Si l'objet n'existe pas, il est créé et renvoyé sous forme de pointeur. S'il existe déjà, le pointeur est alors directement envoyé.

Pourquoi la généricité

Pour contrôler le nombre d'instance d'un objet, il faut implémenter un mécanisme décrit par le patron de conception du singleton.
Cette implémentation n'est pas compliquée en soit, mais si le développeur a plusieurs objets à instance unique, cela devient vite rébarbatif.
C'est là où la généricité intervient :).

En programmation objet, la généricité est un concept qui permet de fournir des algorithmes tout en gardant une indépendance complète vis-à-vis du type de l'objet. Le but est d'offrir un niveau d'abstraction plus élevé.
Concrètement, nous allons voir comment implémenter un mécanisme qui pourra être utilisable sur n'importe quel type d'objet, du moment qu'il hérite de cette classe générique.

Implémentation

Le concept de singleton est connu et n'est pas très compliqué à mettre en œuvre.
Le point le plus important est d'avoir un constructeur privé qui ne pourra pas être appelé directement (donc private).
A la place, on offre une méthode publique statique (appelable sans objet) qui renvoie un pointeur vers l'objet créé.
C'est par ce procédé qu'est géré le nombre d'instanciation, en contrôlant le constructeur.
Cette classe devra être générique afin d'offrir la possibilité au développeur d'utiliser ce mécanisme sans le réécrire à chaque fois, et ce qu'importe le type de l'objet.

Trêve de bavardage, du code !

La classe générique
#include <QDebug>

template <typename T>
class AbulEduSingletonV1
{

protected:
    /* Constructeur et Destructeur en protected pour réimplémentation dans les classes filles */
    AbulEduSingletonV1(){}
    ~AbulEduSingletonV1(){ qDebug() << __FUNCTION__; }

public:
    /* Interface publique */
    static T *getInstance()
    {
        if(NULL == _singleton){
            qDebug() << __FUNCTION__ << "Creation Singleton";
            _singleton = new T;
        }
        else{
            qDebug() << __FUNCTION__ << "Singleton deja existant";
        }
        return (static_cast<T*> (_singleton));
    }

    static void kill()
    {
        if(NULL != _singleton)
        {
            delete _singleton;
            _singleton = NULL;
        }
    }

private:
    /* Instance unique */
    static T *_singleton;
};

template <typename T>
T *AbulEduSingletonV1<T>::_singleton = NULL;

Le mot clé "template"

Une classe générique est une classe comme les autres, elle se différencie seulement par le fait de l'utilisation du template <typename T> et de la lettre T là où machinalement vous auriez mis un type quelconque (string, int, etc...). Cela veut dire que ce mot clé sera remplacé par le type que vous voulez. Magique !

Et le constructeur, il est protected...

Ah oui, j'avais dit que le modèle de conception singleton avait un constructeur privé. Mais le mot clé protected permet à la classe qui en héritera de pouvoir mettre son constructeur privé tout en permettant justement ce mécanisme d'héritage.

Initialisation de l'attribut _singleton en dehors de la classe ?!

Bizarre, mais facilement explicable. Une classe statique permet d'être appelé sans objet. S'il n'y a pas d'objet, il n'y a pas de place réservée en mémoire et donc pas d'initialisation possible d'attributs privés.
La combine est donc de déclarer les initialisations des objets statiques en dehors de la classe.
Une bonne manière est de le faire dans le .h, juste après la déclaration de la classe.

Et si je veux utiliser cette classe générique, comment fait-on ?
/* De par la généricité, il est obligatoire de spécifier le type entre <...> pour l'héritage  */
class MonSingleton : public AbulEduSingletonV1<MonSingleton>{

    /* Déclaration d'une classe amie, pour avoir accès au constructeur privé */
    friend class AbulEduSingletonV1<MonSingleton>;

private:
    MonSingleton(){}
    ~MonSingleton(){}
    int _value;

public:
    /* Méthodes get & set seulement là pour illustrer le concept */
    void setValue(int value){ _value = value;}
    int getValue(){ return _value;}
};

int main()
{
    /* Déclaration de trois objets du type MonSingleton */
    MonSingleton *obj1, *obj2, *obj3;

    obj1 = MonSingleton::getInstance();
    obj2 = MonSingleton::getInstance();
    obj3 = MonSingleton::getInstance();

    obj1->setValue(98);

    qDebug() << obj1->getValue();
    qDebug() << obj2->getValue();
    qDebug() << obj3->getValue();

    exit(0);
}

/////////////////////////////////////////
Les informations de debug :

getInstance Creation Singleton 
getInstance Singleton deja existant 
getInstance Singleton deja existant 
98 
98 
98 

Quelques explications...

La généricité impose de redéfinir le type de l'objet pour correctement hérité de la classe générique.
Ainsi, à la compilation, tout les types T de la classe générique seront remplacés par le type que vous aurez spécifié.

La déclaration de classe "amie" (friend), permet à cette classe seulement d'avoir accès au constructeur et destructeur privé de la classe. De par sa nature, le friend s'oppose au paradigme objet et à l'encapsulation. À utiliser avec parcimonie...

Les méthodes setValue(...) et getValue() ne sont seulement là pour vous prouver que l'objet est unique.

Dans le main, on déclare trois objets que l'on va instancier grâce à la méthode getInstance().
C'est notre contrôleur. La première fois, il va créer l'objet. Les fois suivantes, il va juste nous renvoyer un pointeur sur l'instance en cours. La preuve est que lorsque nous modifions la valeur de obj1, tous les autres objets sont également modifiés. Normal, ils pointent tous sur le même :p.

@author Icham Sirat
@date 2013 09 24
N'hésitez pas à m'envoyer un mail si un passage ne paraît pas clair : <>

Redmine Appliance - Powered by TurnKey Linux