JavaRush /Blog Java /Random-FR /Modèles de conception : Singleton

Modèles de conception : Singleton

Publié dans le groupe Random-FR
Bonjour! Aujourd'hui, nous allons examiner de plus près différents modèles de conception, et nous commencerons par le modèle Singleton, également appelé « singleton ». Modèles de conception : Singleton - 1Rappelons-nous : que savons-nous des modèles de conception en général ? Les modèles de conception sont des bonnes pratiques qui peuvent être suivies pour résoudre un certain nombre de problèmes connus. Les modèles de conception ne sont généralement liés à aucun langage de programmation. Considérez-les comme un ensemble de recommandations, à la suite desquelles vous pourrez éviter les erreurs et ne pas réinventer la roue.

Qu'est-ce qu'un singleton ?

Un singleton est l’un des modèles de conception les plus simples pouvant être appliqués à une classe. Les gens disent parfois « cette classe est un singleton », ce qui signifie que cette classe implémente le modèle de conception singleton. Parfois, il est nécessaire d'écrire une classe pour laquelle un seul objet peut être créé. Par exemple, une classe chargée de la journalisation ou de la connexion à une base de données. Le modèle de conception Singleton décrit comment nous pouvons accomplir une telle tâche. Un singleton est un modèle de conception qui fait deux choses :
  1. Fournit une garantie qu’une classe n’aura qu’une seule instance de la classe.

  2. Fournit un point d’accès global à une instance de cette classe.

Par conséquent, il existe deux fonctionnalités caractéristiques de presque toutes les implémentations du modèle singleton :
  1. Constructeur privé. Restreint la possibilité de créer des objets de classe en dehors de la classe elle-même.

  2. Une méthode statique publique qui renvoie une instance de la classe. Cette méthode est appelée getInstance. Il s'agit du point d'accès global à l'instance de classe.

Options de mise en œuvre

Le modèle de conception singleton est utilisé de différentes manières. Chaque option est bonne et mauvaise à sa manière. Ici, comme toujours : il n'y a pas d'idéal, mais il faut s'efforcer de l'atteindre. Mais tout d’abord, définissons ce qui est bon et ce qui est mauvais, et quelles métriques influencent l’évaluation de la mise en œuvre d’un modèle de conception. Commençons par le positif. Voici les critères qui confèrent à la mise en œuvre jutosité et attractivité :
  • Initialisation paresseuse : lorsqu'une classe est chargée alors que l'application est en cours d'exécution exactement au moment où elle est nécessaire.

  • Simplicité et transparence du code : la métrique, bien sûr, est subjective, mais importante.

  • Sécurité des threads : fonctionne correctement dans un environnement multithread.

  • Hautes performances dans un environnement multithread : les threads se bloquent peu ou pas du tout lors du partage d'une ressource.

Maintenant les inconvénients. Listons les critères qui montrent la mise en œuvre sous un mauvais jour :
  • Initialisation non paresseuse : lorsqu'une classe est chargée au démarrage de l'application, qu'elle soit nécessaire ou non (un paradoxe, dans le monde informatique il vaut mieux être paresseux)

  • Complexité et mauvaise lisibilité du code. La métrique est également subjective. Nous supposerons que si le sang vient des yeux, la mise en œuvre est médiocre.

  • Manque de sécurité des fils. En d’autres termes, « risque de fil ». Fonctionnement incorrect dans un environnement multithread.

  • Mauvaises performances dans un environnement multi-thread : les threads se bloquent tout le temps ou souvent lors du partage d'une ressource.

Code

Nous sommes maintenant prêts à envisager diverses options de mise en œuvre, en énumérant les avantages et les inconvénients :

Solution simple

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }
}
La mise en œuvre la plus simple. Avantages:
  • Simplicité et transparence du code

  • Sécurité du fil

  • Hautes performances dans un environnement multithread

Inconvénients :
  • Pas d’initialisation paresseuse.
Pour tenter de corriger le dernier défaut, nous obtenons l’implémentation numéro deux :

Initialisation paresseuse

public class Singleton {
  private static Singleton INSTANCE;

  private Singleton() {}

  public static Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
Avantages:
  • Initialisation paresseuse.

Inconvénients :
  • Pas sûr pour les threads

La mise en œuvre est intéressante. Nous pouvons initialiser paresseusement, mais nous avons perdu la sécurité des threads. Pas de problème : dans la troisième implémentation, nous synchronisons tout.

Accesseur synchronisé

public class Singleton {
  private static Singleton INSTANCE;

  private Singleton() {
  }

  public static synchronized Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
Avantages:
  • Initialisation paresseuse.

  • Sécurité du fil

Inconvénients :
  • Mauvaises performances dans un environnement multithread

Super! Dans l’implémentation numéro trois, nous avons rétabli la sécurité des threads ! C'est vrai, c'est lent... Désormais getInstance, la méthode est synchronisée et vous ne pouvez la saisir qu'une à la fois. En fait, nous n'avons pas besoin de synchroniser la méthode entière, mais seulement la partie dans laquelle nous initialisons un nouvel objet de classe. Mais nous ne pouvons pas simplement envelopper synchronizedla partie responsable de la création d'un nouvel objet dans un bloc : cela n'assurera pas la sécurité des threads. C'est un peu plus compliqué. La méthode de synchronisation correcte est indiquée ci-dessous :

Verrouillage vérifié deux fois

public class Singleton {
    private static Singleton INSTANCE;

  private Singleton() {
  }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}
Avantages:
  • Initialisation paresseuse.

  • Sécurité du fil

  • Hautes performances dans un environnement multithread

Inconvénients :
  • Non pris en charge sur les versions Java inférieures à 1.5 (le mot clé volatile a été corrigé dans la version 1.5)

Je note que pour que cette option d'implémentation fonctionne correctement, l'une des deux conditions suivantes est requise. La variable INSTANCEdoit être soit final, soit volatile. La dernière implémentation dont nous discuterons aujourd'hui est Class Holder Singleton.

Titulaire de la classe Singleton

public class Singleton {

   private Singleton() {
   }

   private static class SingletonHolder {
       public static final Singleton HOLDER_INSTANCE = new Singleton();
   }

   public static Singleton getInstance() {
       return SingletonHolder.HOLDER_INSTANCE;
   }
}
Avantages:
  • Initialisation paresseuse.

  • Sécurité du fil.

  • Hautes performances dans un environnement multithread.

Inconvénients :
  • Pour un fonctionnement correct, il est nécessaire de garantir que l'objet de classe Singletonest initialisé sans erreur. Sinon, le premier appel de méthode getInstancese terminera par une erreur ExceptionInInitializerErroret tous les suivants échoueront NoClassDefFoundError.

La mise en œuvre est presque parfaite. Et paresseux, thread-safe et rapide. Mais il y a une nuance décrite dans le moins. Tableau comparatif des différentes implémentations du modèle Singleton :
Mise en œuvre Initialisation paresseuse Sécurité du fil Vitesse multithread Quand utiliser?
Solution simple - + Rapide Jamais. Ou lorsque l'initialisation paresseuse n'est pas importante. Mais jamais mieux.
Initialisation paresseuse + - N'est pas applicable Toujours lorsque le multithreading n'est pas nécessaire
Accesseur synchronisé + + Lentement Jamais. Ou lorsque la vitesse de travail avec le multithreading n'a pas d'importance. Mais jamais mieux
Verrouillage vérifié deux fois + + Rapide Dans de rares cas où vous devez gérer des exceptions lors de la création d'un singleton. (lorsque le détenteur de classe Singleton n'est pas applicable)
Titulaire de la classe Singleton + + Rapide Toujours lorsque le multithreading est nécessaire et qu'il existe une garantie qu'un objet de classe singleton sera créé sans problème.

Avantages et inconvénients du modèle Singleton

En général, le singleton fait exactement ce qu’on attend de lui :
  1. Fournit une garantie qu’une classe n’aura qu’une seule instance de la classe.

  2. Fournit un point d’accès global à une instance de cette classe.

Cependant, ce modèle présente des inconvénients :
  1. Singleton viole le SRP (Single Responsibility Principe) - la classe Singleton, en plus de ses responsabilités immédiates, contrôle également le nombre de ses copies.

  2. La dépendance d'une classe ou d'une méthode régulière sur un singleton n'est pas visible dans le contrat public de la classe.

  3. Les variables globales sont mauvaises. Le singleton finit par se transformer en une grande variable globale.

  4. La présence d'un singleton réduit la testabilité de l'application en général et des classes qui utilisent le singleton en particulier.

OK, c'est fini maintenant. Nous avons examiné le modèle de conception singleton. Désormais, dans une conversation pour la vie avec vos amis programmeurs, vous pourrez dire non seulement ce qui est bon, mais aussi quelques mots sur ce qui ne va pas. Bonne chance pour maîtriser de nouvelles connaissances.

Lecture complémentaire :

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION