Aller au contenu

Singleton (patron de conception)

Un article de Wikipédia, l'encyclopédie libre.

En génie logiciel, le singleton est un patron de conception (design pattern), appartenant à la catégorie des patrons de création, dont l'objectif est de restreindre l'instanciation d'une classe à un seul objet. On fournira alors un accès global à celui-ci. Il s'agit d'un des patrons de création les plus simples mais les plus couramment utilisés[1].

Il est utilisé lorsqu'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 un seul objet 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é, 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 threads 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[2].

Diagramme de classes

[modifier | modifier le code]

Implémentations

[modifier | modifier le code]

Voici une class template écrite en D du pattern singleton :

class Singleton(T)  
{
	public
	{
		static T getSingleton() 
		{

			if(_instance is null) 
				_instance = new T;
			
			return _instance;
		}
	}
	private
	{
		this()
		{
		     	_instance = cast(T) this;
		}
		~this()
		{}
	
		static T _instance; 
	}
}

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

 // La classe est finale, car un singleton n'est pas censé avoir d'héritier.
 public final class Singleton {

     // L'utilisation du mot clé volatile, en Java version 5 et supérieure,
     // empêche les effets de bord dus aux copies locales de l'instance qui peuvent être modifiées dans le thread principal.
     // De Java version 1.2 à 1.4, il est possible d'utiliser la classe ThreadLocal.
     private static volatile Singleton instance = null;
 
     // D'autres attributs, classiques et non "static".
     private String ---;
     private int zzz;

     /**
      * Constructeur de l'objet.
      */
     private Singleton() {
         // La présence d'un constructeur privé supprime le constructeur public par défaut.
         // De plus, seul le singleton peut s'instancier lui-même.
         super();
     }
     
     /**
      * Méthode permettant de renvoyer une instance de la classe Singleton
      * @return Retourne l'instance du singleton.
      */
     public final static Singleton getInstance() {
         //Le "Double-Checked Singleton"/"Singleton doublement vérifié" permet 
         //d'éviter un appel coûteux à synchronized, 
         //une fois que l'instanciation est faite.
         if (Singleton.instance == null) {
            // Le mot-clé synchronized sur ce bloc empêche toute instanciation
            // multiple même par différents "threads".
            // Il est TRES important.
            synchronized(Singleton.class) {
              if (Singleton.instance == null) {
                Singleton.instance = new Singleton();
              }
            }
         }
         return Singleton.instance;
     }

     // D'autres méthodes classiques et non "static".
     public void faire---(...) {
       ...
       this.xxx = "bonjour";
     }

     public void faireZzz(...) {
       ...
     }
    
 }

Pour plus d'information, sur ce patron : Cf The "Double-Checked Locking is Broken" Declaration[3].

Attention, l'usage du double checked locking est une mauvaise idée en Java. Ça ne fonctionne simplement pas dans un environnement multithread. Idem pour « volatile ».

Exemple d'utilisation :

 public static void main(String[] args) {
   // Il ne faut pas copier un singleton dans une variable locale sauf dans les cas d'optimisations extrêmes.
   Singleton.getInstance().faire---(1,2,3);
   Singleton.getInstance().faireZzz(3,4,5);
 }

Il est à noter que l'usage du pattern singleton en Java n'est pas des plus approprié car l’instanciation tardive de la classe que l'on cherche à obtenir est en fait le comportement par défaut du Java et le pattern "standard" risque juste de générer des bugs et des complications. En effet le ClassLoader de la JVM va charger les classes à la première utilisation et donc :

  1. Le mutex (la variable du synchronized) quel qu'il soit est créé à ce moment et donc est très artificiel ;
  2. La classe est nativement un singleton sur lequel il est naturel de s’appuyer.

À partir de cette remarque plusieurs implémentations sont possibles :

  1. Initialisation directe de la variable instance.
public final class Singleton {
  private final static Singleton instance = new Singleton();
  public final static Singleton getInstance() { return instance; }
  private Singleton() {}
}
  1. Initialisation dans un bloc "static".
public final class Singleton {
  private static Singleton instance = null;
  static {
    instance = new Singleton();
  }
  public final static Singleton getInstance() { return instance; }
  private Singleton() {}
}
  1. Utilisation d'un "enum" à la place d'une classe.
public enum Singleton {
  SINGLETON;
}

L'utilisation se fait par appel de la seule valeur de l'enum :

Singleton.SINGLETON;

Cette dernière forme est fortement recommandée depuis Java 1.5[4],[5], mais l'utilisation d'une énumération étant gourmande, ce n'est donc pas l'idéal si le but recherché est la performance.

À noter que l'unicité du singleton est dépendante de l'unicité du ClassLoader.

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 jusqu'en C++11[6] ; 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;
     }
 };
 
 class OnlyOne : public Singleton<OnlyOne>
 {
     // constructeurs/destructeur de OnlyOne accessibles au Singleton
     friend class Singleton<OnlyOne>; 
     //...définir ici le reste de l'interface
 };

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.

Une autre façon de voir les choses est de mettre un booléen en statique dans la classe, qui indique si une instance est déjà créée, et de refuser la création si c'est le cas :

class Singleton
{
public:
    Singleton()
    {
        if (alreadyCreated)
            throw logic_error("Vous ne pouvez pas créer une seconde instance de la classe Singleton.");
        // Sinon, on construit la classe et on déclare l'objet créé
        alreadyCreated = true;
    }
    ~Singleton()
    {
        // Si on veut pouvoir recréer la classe plus tard, on déclare l'objet non existant
        alreadyCreated = false;
    }
    
protected:
    static bool alreadyCreated;
};
// Et on déclare alreadyCreated comme faux au début !
bool Singleton::alreadyCreated = false;

Cette méthode permet aussi de limiter un nombre d'instances, en remplaçant le booléen par un nombre, et le test par une comparaison.

Voici une classe Eiffel utilisant le pattern singleton :

class SINGLETON

create	make

feature {NONE} -- Initialisation

	make
		require
			Not_Already_Created: not already_created.item
		do
			already_created.put (True)
		end

feature -- Singleton

	already_created:CELL[BOOLEAN]
		once ("PROCESS")
			create Result.put(False)
		end

end
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

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

        ' On retourne l'instance de Singleton
        Return instance

    End Function

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

    private Singleton() 
    {
		
    }

    public static Singleton Instance
    {
        get
        {
            if (_instance == null) //Les locks prennent du temps, il est préférable de vérifier d'abord la nullité de l'instance.
            {
                lock (instanceLock)
                {
	            if (_instance == null) //on vérifie encore, au cas où l'instance aurait été créée entretemps.
	               _instance = new Singleton();
                }
            }
            return _instance;
        }
    }
}

// Autre implémentation possible, depuis le .Net 4, sans lock
public sealed class Singleton
{
    private static readonly Lazy<Singleton> _lazy = new Lazy<Singleton>(() => new Singleton());
    
    public static Singleton Instance { get { return _lazy.Value; } }

    private Singleton()
    {
    }
}

Objective-C

[modifier | modifier le code]
+ (instancetype)sharedInstance
{
	static dispatch_once_t pred;
	static MyClass * sharedInstance = nil;
	dispatch_once(&pred, ^{
            sharedInstance = [self new];
        });
	return sharedInstance;
}

ActionScript

[modifier | modifier le code]
public class Singleton
{
	private static var _instance:Singleton;
	
	public static function getInstance():Singleton{
		if (_instance== null) {
			_instance= new Singleton();
		}
		
		return _instance;
	}
	
	public function Singleton() {
		
	}
}

ActionScript 3

[modifier | modifier le code]

Une autre implémentation du singleton en AS3 qui prend en compte les spécificités du langage et surtout la définition du pattern

Déclaration d'une constante de type SingletonImpl :

package patterns {
	public const Singleton:SingletonImpl = new SingletonImpl();
}

L'implémentation proprement dite du Singleton :

package patterns {
	internal class SingletonImpl {
                private static var instancied:Boolean = false;
                public function SingletonImpl()
		{
			if(instancied)
				throw new Error ("Pattern Singleton: only one instance is permit");
				
			instancied = true;
		}

		public function log(msg:String):void {
			trace(msg);
		}
	}
}

Son utilisation est plutôt simple :

Singleton.log("Toto");

Implémentation simple

[modifier | modifier le code]
class  Singleton (object):
    instance = None       # Attribut statique de classe
    def __new__(cls): 
        "méthode de construction standard en Python"
        if cls.instance is None:
            cls.instance = object.__new__(cls)
        return cls.instance

# Utilisation
monSingleton1 =  Singleton()
monSingleton2 =  Singleton()

# monSingleton1 et monSingleton2 renvoient à la même instance
assert monSingleton1 is monSingleton2

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 code]

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 [7], 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 parti du nouveau type de classe de Python[8], 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]

Le patron singleton existe dans la bibliothèque 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

La sortie de ce script est « foo ».

Voici une solution écrite en PHP :

class Singleton {

    private static $_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 () {
        if (!(self::$_instance instanceof self))
            self::$_instance = new self();

        return self::$_instance;
    }

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

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

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.

Depuis PHP 5.3, la classe suivante fonctionne et est héritable[9] :

class Singleton
{
    private static $instances = array();
    
    final private function __construct()
    {
    }
    
    final public function __clone()
    {
        trigger_error("Le clonage n'est pas autorisé.", E_USER_ERROR);
    }
    
    final public static function getInstance()
    {
        $c = get_called_class();
       
        if(!isset(self::$instances[$c]))
        {
            self::$instances[$c] = new $c;
        }
       
        return self::$instances[$c];
    }
}

Il est aussi possible d'utiliser static::, bien plus adapté.

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 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

Notes et références

[modifier | modifier le code]

Sur les autres projets Wikimedia :

  1. (fr) ionos, « Le singleton pattern : une classe à part », sur IONOS by 1&1, (consulté le ).
  2. (fr) refactoring.guru, « Singleton », sur refactoring.guru (consulté le ).
  3. (en) David Bacon (IBM Research) Joshua Bloch (Javasoft), Jeff Bogda, Cliff Click (Hotspot JVM project), Paul Haahr, Doug Lea, Tom May, Jan-Willem Maessen, Jeremy Manson, John D. Mitchell (jGuru) Kelvin Nilsen, Bill Pugh, Emin Gün Sirer, « The "Double-Checked Locking is Broken" Declaration ».
  4. (en) http://stackoverflow.com/questions/427902/what-is-the-best-approach-for-using-an-enum-as-a-singleton-in-java
  5. (en) http://stackoverflow.com/questions/70689/what-is-an-efficient-way-to-implement-a-singleton-pattern-in-java
  6. Working Draft, Standard for Programming Language C++, §6.7 Declaration statement [stmt.dcl] p4
  7. (en) Alex Martelli, « Singleton? We don't need no stinkin' singleton: the Borg design pattern », ASPN Python Cookbook (consulté le ).
  8. (en) « New-style classes », Python documentation.
  9. Héritage d'un singleton sur le manuel PHP(en)