Patrons de conception/Singleton

Un livre de Wikilivres.
Patron de conception
Catégorie : « Gang of Four »Création
Nom français : Singleton
Nom anglais : Singleton
Ne permettre la création que d'une seule instance d'une classe


Le singleton est un patron de conception dont l'objet est de restreindre l'instanciation d'une classe à un seul objet (ou bien à quelques objets seulement). Il est utilisé lorsque l'on a besoin d'exactement un objet pour coordonner des opérations dans un système. Le modèle est parfois utilisé pour son efficacité, lorsque le système est plus rapide ou occupe moins de mémoire avec peu d'objets qu'avec beaucoup d'objets similaires.

On implémente le singleton en écrivant une classe contenant une méthode qui crée une instance uniquement s'il n'en existe pas encore. Sinon elle renvoie une référence vers l'objet qui existe déjà. Dans beaucoup de langages de type objet, il faudra veiller à ce que le constructeur de la classe soit privé ou bien protégé, afin de s'assurer que la classe ne puisse être instanciée autrement que par la méthode de création contrôlée.

Le singleton doit être implémenté avec précaution dans les applications multi-thread. Si deux processus légers exécutent en même temps la méthode de création alors que l'objet unique n'existe pas encore, il faut absolument s'assurer qu'un seul créera l'objet, et que l'autre obtiendra une référence vers ce nouvel objet.

La solution classique à ce problème consiste à utiliser l'exclusion mutuelle pour indiquer que l'objet est en cours d'instanciation.

Dans un langage à base de prototypes, où sont utilisés des objets mais pas des classes, un singleton désigne seulement un objet qui n'a pas de copies, et qui n'est pas utilisé comme prototype pour d'autres objets.

Diagramme de classes UML[modifier | modifier le wikicode]

La figure ci-dessous donne le diagramme de classes UML du patron de conception Singleton.

Diagramme de classes UML du patron de conception Singleton

Implémentations[modifier | modifier le wikicode]

Java[modifier | modifier le wikicode]

Voici une solution écrite en Java (il faut écrire un code similaire pour chaque classe-singleton) :

public class Singleton
{
    private static Singleton INSTANCE = null;

    /*
     * La présence d'un constructeur privé supprime
     * le constructeur public par défaut.
     */
    private Singleton() {}

    /*
     * Le mot-clé synchronized sur la méthode de création
     * empêche toute instanciation multiple même par
     * différents threads.
     * Retourne l'instance du singleton.
     */
    public synchronized static Singleton getInstance()
    {
        if (INSTANCE == null)
            INSTANCE = new Singleton();
        return INSTANCE;
    }
}

Une solution variante existe cependant. Elle consiste à alléger le travail de la méthode getInstance en déplaçant la création de l'instance unique au niveau de la déclaration de la variable référant l'instance unique :

public class Singleton
{
    /*
     * Création de l'instance au niveau de la variable.
     */
    private static final Singleton INSTANCE = new Singleton();

    /*
     * La présence d'un constructeur privé supprime
     * le constructeur public par défaut.
     */
    private Singleton() {}

    /*
     * Dans ce cas présent, le mot-clé synchronized n'est pas utile.
     * L'unique instanciation du singleton se fait avant
     * l'appel de la méthode getInstance(). Donc aucun risque d'accès concurrents.
     * Retourne l'instance du singleton.
     */
    public static Singleton getInstance()
    {
        return INSTANCE;
    }
}

À noter que la première implémentation est plus lente, étant donné que la méthode getInstance est synchronisée, les processus doivent faire la queue alors que le synchronized n'est utile qu'au premier appel, après l'instanciation, il n'y a pas d'erreur possible, toutefois il n'y a pas d'autres possibilités : faire un verrouillage au niveau de l'objet dans la méthode ne suffit pas[1].

La dernière implémentation a une faille, il est possible, en sérialisant puis en désérialisant la classe d'obtenir une seconde instance[2]. Une troisième implémentation permet de pallier ce problème, le singleton est l'unique élément d'une énumération.

public enum Singleton implements Serializable
{
    INSTANCE;
}

C++[modifier | modifier le wikicode]

Voici une implémentation possible en C++, connue sous le nom de "singleton de Meyers". Le singleton est un objet static et local. Attention : cette solution n'est pas sûre dans un contexte multi-thread ; elle sert plutôt à donner une idée du fonctionnement d'un singleton qu'à être réellement utilisée dans un grand projet logiciel. Aucun constructeur ou destructeur ne doit être public dans les classes qui héritent du singleton.

template<typename T> class Singleton
{
  public:
    static T& Instance()
    {
        static T theSingleInstance; // suppose que T a un constructeur par défaut
        return theSingleInstance;
    }// ceci n'est pas valide dans un contexte multi-thread
};

class OnlyOne : public Singleton<OnlyOne>
{
    // constructeurs/destructeur de OnlyOne accessibles au Singleton
    friend class Singleton<OnlyOne>; 
    //...définir ici le reste de l'interface
};

VB.Net[modifier | modifier le wikicode]

Public Class Singleton

    ' Variable locale pour stocker une référence vers l'instance
    Private Shared instance As Singleton = Nothing
    Private Shared ReadOnly mylock As New Object()


    ' Le constructeur est Private
    Private Sub New()
        '
    End Sub

    ' La méthode GetInstance doit être Shared
    Public Shared Function GetInstance() As Singleton

        SyncLock (mylock)
            ' Si pas d'instance existante on en crée une...
            If instance Is Nothing Then
                instance = New Singleton
            End If

            ' On retourne l'instance de Singleton
            Return instance
        End SyncLock

    End Function

End Class

C#[modifier | modifier le wikicode]

public class Singleton
{
    private static Singleton _instance;
    static readonly object instanceLock = new object();

    private Singleton() 
    {
		
    }

    public static Singleton getInstance() 
    {
        lock (instanceLock)
        {
	    if (_instance == null) 
	       _instance = new Singleton();
	    
	
	    return _instance;
        }
    }
}

ActionScript[modifier | modifier le wikicode]

public class Singleton
{
	private static var _instance:Singleton;
	
	public static function getInstance():Singleton{
		if (_instance== null) {
			_instance= new Singleton();
		}
		
		return _instance;
	}
	
	public function Singleton() {
		
	}
}

Python[modifier | modifier le wikicode]

Implémentation simple[modifier | modifier le wikicode]

 class  Singleton(object):
    _instance = None       # Attribut statique de classe
    def __new__(cls): # __new__ classmethod implicite: la classe 1e paramètre
        "méthode de construction standard en Python"
        if cls._instance is None:
            cls._instance = object.__new__(cls)
        return cls._instance

# Utilisation
mon_singleton1 =  Singleton()
mon_singleton2 =  Singleton()

# mon_singleton1 et mon_singleton2 renvoient à la même instance
assert mon_singleton1 is mon_singleton2

Les deux variables référencent ici la même instance. Cette solution ne nécessite pas de méthode spécifique pour accéder à l'instance de classe (comparativement à d'autres langages, où il est d'usage d'implémenter une méthode getInstance(), par exemple).

Considérations "avancées"[modifier | modifier le wikicode]

D'après le développeur Python Alex Martelli, le patron de conception Singleton a un nom élégant mais trompeur, car il met l'accent sur l'identité plutôt que sur l'état de l'objet. D'où l'apparition d'un patron alternatif, appelé Borg [3], pour lequel toutes les instances partagent le même état. Il est généralement accepté par la communauté de développeurs Python que le partage d'état entre instances est plus élégant que la mise en cache de la création d'instances identiques lors de l'initialisation de la classe.

L'implémentation de cet état partagé est quasiment transparente en Python :

class Borg:
   __shared_state = {} # variable de classe contenant l'état à partager
   def __init__(self):
       # copie de l'état lors de l'initialisation d'une nouvelle instance
       self.__dict__ = self.__shared_state 
   # suivi de ce qui est nécessaire à la classe, et c'est tout !

L'implémentation ci-dessus peut être améliorée en tirant partie du nouveau type de classe de Python [4], ne nécessitant ainsi qu'une seule instance :

class Singleton (object):
   instance = None       
    def __new__(classe, *args, **kargs): 
        if classe.instance is None:
            classe.instance = object.__new__(classe, *args, **kargs)
        return classe.instance

# Utilisation:
monSingleton1 =  Singleton()
monSingleton2 =  Singleton()
 
# monSingleton1 et  monSingleton2 renvoient à la même instance.
assert monSingleton1 is monSingleton2
  • La méthode __init__ est exécutée pour chaque appel à Singleton().
  • Afin de permettre à une classe d'hériter d'un Singleton, la variable de classe instance devrait être un dictionnaire Python appartenant explicitement à la classe Singleton, comme illustré ci-dessous :
class InheritableSingleton (object):
    # Dictionnaire Python référencant les instances déjà créés
    instances = {}
    def __new__(cls, *args, **kargs): 
        if InheritableSingleton.instances.get(cls) is None:
            InheritableSingleton.instances[cls] = object.__new__(cls, *args, **kargs)
        return InheritableSingleton.instances[cls]

Ruby[modifier | modifier le wikicode]

Le patron Singleton existe dans la librairie standard du langage Ruby. C'est un mixin qu'il suffit d'inclure dans la classe qui doit être un singleton.

require 'singleton'

class Config
  include Singleton

  def foo
    puts 'foo'
  end
end

config = Config.instance

config.foo

Ce script affiche "foo".

PHP 5[modifier | modifier le wikicode]

Voici une solution écrite en PHP :

class Singleton {
    
    private $instance;
    
    /**
     * Empêche la création externe d'instances.
     */
    private function __construct () {}

    /**
     * Empêche la copie externe de l'instance.
     */
    private function __clone () {}

    /**
     * Renvoi de l'instance et initialisation si nécessaire.
     */
    public static function getInstance() {
        static $instance;
 
        if ($instance === null) {
            $instance = new self();
        }
 
        return $instance;
    }

    /**
     * Méthodes dites métier
     */
    public function uneAction () {}
}

// Utilisation
Singleton::getInstance()->uneAction();

Avant PHP 5.3 : Difficultés liées à l'héritage d'une classe Singleton[modifier | modifier le wikicode]

Il faut noter qu'avant PHP 5.3, il n'est pas possible d'hériter une classe de type Singleton. En effet, l'accesseur self:: se réfère toujours à la classe dans laquelle il est écrit. L'exemple ci-dessous illustre ce problème.

class Singleton {
    ... // Voir ci-dessus (remplacer la visibilité private par protected)
    
    /**
     * Méthodes dites métier
     */
    public function uneAction() {
        echo "Singleton";
    }
}

class AnotherSingleton extends Singleton {
    public function uneAction() {
        echo "AnotherSingleton";
    }
}

echo AnotherSingleton::getInstance()->uneAction(); // Affiche "Singleton" au lieu de "AnotherSingleton"

Ce problème ne peut être résolu qu'en dupliquant la méthode getInstance dans la classe héritée.

class AnotherSingleton extends Singleton {
    
    public static function getInstance() {
        static $instance;

        if ($instance === null) {
            $instance = new self();
        }

        return $instance;
    }
    
    ... // Voir ci-dessus
}

echo AnotherSingleton::getInstance()->uneAction(); // Affiche bien "AnotherSingleton"

Depuis PHP 5.3 : le mot-clé static et le Late Static Bindings[modifier | modifier le wikicode]

Avec PHP 5.3, l'apparition du Late Static Bindings sous la forme d'un accesseur static:: semblable à self:: résout ce problème. En effet, l'accesseur static:: se réfère à la classe dans laquelle il est appelé. Il peut donc être hérité. C'est pourquoi l'on parle de Late Static Bindings (Résolution statique à la volée), par opposition à une résolution statique au moment de la compilation du script.

class Singleton {
    ... // Voir ci-dessus (remplacer la visibilité private par protected)
    
    final public static function getInstance() {
        static $instance;

        if ($instance === null) {
            $instance = new static();
        }

        return $instance;
    }
    
    ... // Voir ci-dessus
}

echo AnotherSingleton::getInstance()->uneAction(); // Affiche bien "AnotherSingleton"

Perl[modifier | modifier le wikicode]

Pour les versions de Perl à partir de 5.10, une variable d'état fait l'affaire.

package MySingletonClass;
use strict;
use warnings;
use 5.10;

sub new {
    my ($class) = @_;
    state $the_instance;

    if (! defined $the_instance) {
        $the_instance = bless { }, $class;
    }
    return $the_instance;
}

Pour les versions plus anciennes, c'est une fermeture (closure en anglais) qui fera l'affaire.

package MySingletonClass;
use strict;
use warnings;

my $THE_INSTANCE;
sub new {
    my ($class) = @_;

    if (! defined $THE_INSTANCE) {
        $THE_INSTANCE = bless { }, $class;
    }
    return $THE_INSTANCE;
}

Si le module Moose est utilisé, il y a l'extension MooseX::Singleton

Voir aussi[modifier | modifier le wikicode]

  • Objet nul pouvant utiliser le patron singleton.

Notes et références[modifier | modifier le wikicode]

Liens externes[modifier | modifier le wikicode]

Divers[modifier | modifier le wikicode]