Alternatives au modèle singleton

27

J'ai lu différentes opinions sur le motif singleton. Certains soutiennent qu'il devrait être évité à tout prix et d'autres qu'il peut être utile dans certaines situations.

Une situation dans laquelle j'utilise des singletons est quand j'ai besoin d'une fabrique (disons un objet f de type F) pour créer des objets d'une certaine classe A. La fabrique est créée une fois à l'aide de certains paramètres de configuration, puis est utilisée chaque fois qu'un objet de le type A est instancié. Ainsi, chaque partie du code qui veut instancier A récupère le singleton f et crée la nouvelle instance, par exemple

F& f                   = F::instance();
boost::shared_ptr<A> a = f.createA();

Donc, le scénario général est que

  1. Je n'ai besoin que d'une seule instance d'une classe soit pour des raisons d'optimisation (je n'ai pas besoin de plusieurs objets d'usine) ou pour partager un état commun (par exemple, l'usine sait combien d'instances de A elle peut encore créer)
  2. J'ai besoin d'un moyen d'avoir accès à cette instance f de F à différents endroits du code.

Je ne souhaite pas savoir si ce modèle est bon ou mauvais, mais en supposant que je souhaite éviter d'utiliser un singleton, quel autre modèle puis-je utiliser?

Les idées que j'avais étaient (1) d'obtenir l'objet d'usine à partir d'un registre ou (2) de créer l'usine à un moment donné pendant le démarrage du programme, puis de transmettre l'usine comme paramètre.

Dans la solution (1), le registre lui-même est un singleton, donc je viens de déplacer le problème de ne pas utiliser un singleton de l'usine au registre.

Dans le cas (2), j'ai besoin d'une source initiale (objet) d'où provient l'objet d'usine, j'ai donc peur de retomber à un autre singleton (l'objet qui fournit mon instance d'usine). En suivant cette chaîne de singletons, je peux peut-être réduire le problème à un singleton (toute l'application) par lequel tous les autres singletons sont directement ou indirectement gérés.

Cette dernière option (en utilisant un singleton initial qui crée tous les autres objets uniques et injecte tous les autres singletons aux bons endroits) serait-elle une solution acceptable? Est-ce la solution qui est implicitement suggérée lorsque l'on conseille de ne pas utiliser de singletons, ou quelles sont les autres solutions, par exemple dans l'exemple illustré ci-dessus?

MODIFIER

Puisque je pense que le point de ma question a été mal compris par certains, voici quelques informations supplémentaires. Comme expliqué par exemple ici , le mot singleton peut indiquer (a) une classe avec un objet instance unique et (b) un modèle de conception utilisé pour créer et accéder à un tel objet.

Pour clarifier les choses, utilisons le terme objet unique pour (a) et motif singleton pour (b). Donc, je sais ce que sont le modèle singleton et l'injection de dépendances (BTW, dernièrement, j'utilise fortement DI pour supprimer les instances du modèle singleton d'un code sur lequel je travaille).

Mon point est qu'à moins que le graphe d'objet entier soit instancié à partir d'un seul objet vivant sur la pile de la méthode principale, il sera toujours nécessaire d'accéder à certains objets uniques via le modèle singleton.

Ma question est de savoir si la création et le câblage d'un graphique d'objet complet dépendent de la méthode principale (par exemple via un cadre DI puissant qui n'utilise pas le modèle lui-même) est la seule solution sans modèle singleton .

Giorgio
la source
8
Injection de dépendance ...
Falcon
1
@Falcon: Injection de dépendance ... quoi? DI ne fait rien pour résoudre ce problème, bien qu'il vous donne souvent un moyen pratique de le cacher.
pdr
@Falcon, tu veux dire conteneur IoC? Cela vous permettra de résoudre les dépendances sur une seule ligne. Par exemple, Dependency.Resolve <IFoo> (); ou Dependency.Resolve <IFoo> () .DoSomething ();
CodeART
Ceci est une assez vieille question, et déjà répondu. Je pense toujours qu'il serait bon de lier celui-ci: stackoverflow.com/questions/162042/…
TheSilverBullet

Réponses:

7

Votre deuxième option est une bonne façon de procéder - c'est une sorte d'injection de dépendance, qui est le modèle utilisé pour partager l'état dans votre programme lorsque vous voulez éviter les singletons et les variables globales.

Vous ne pouvez pas contourner le fait que quelque chose doit créer votre usine. Si ce quelque chose se trouve être l'application, qu'il en soit ainsi. Le point important est que votre usine ne devrait pas se soucier de quel objet l'a créée, et les objets qui reçoivent l'usine ne devraient pas dépendre de la provenance de l'usine. Ne laissez pas vos objets obtenir un pointeur vers le singleton d'application et lui demander la fabrique; Demandez à votre application de créer l'usine et de la donner aux objets qui en auront besoin.

Caleb
la source
17

Le but d'un singleton est de faire en sorte qu'une seule instance puisse jamais exister dans un certain domaine. Cela signifie qu'un singleton est utile si vous avez de bonnes raisons d'appliquer le comportement de singleton; dans la pratique, c'est rarement le cas cependant, et le multi-traitement et le multi-thread brouille certainement le sens de «unique» - est-ce une instance par machine, par processus, par thread, par requête? Et votre implémentation singleton prend-elle en compte les conditions de concurrence?

Au lieu de singleton, je préfère utiliser l'un des deux:

  • instances locales de courte durée, par exemple pour une usine: les classes d’usine typiques ont un minimum d’état, le cas échéant, et il n’ya aucune raison réelle de les maintenir en vie une fois qu’elles ont atteint leur objectif; les frais généraux de création et de suppression de classes ne sont pas à craindre dans 99% de tous les scénarios du monde réel
  • passer une instance, par exemple pour un gestionnaire de ressources: celles-ci doivent être de longue durée, car le chargement des ressources est coûteux et vous voulez les garder en mémoire, mais il n'y a absolument aucune raison d'empêcher la création d'autres instances - qui sait, peut-être que cela aura du sens d'avoir un deuxième gestionnaire de ressources dans quelques mois ...

La raison en est qu'un singleton est un état global déguisé, ce qui signifie qu'il introduit un haut degré de couplage dans toute l'application - n'importe quelle partie de votre code peut saisir l'instance de singleton de n'importe où avec un minimum d'effort. Si vous utilisez des objets locaux ou passez des instances, vous avez beaucoup plus de contrôle et vous pouvez garder vos étendues petites et vos dépendances étroites.

tdammers
la source
3
C'est une très bonne réponse mais je ne demandais pas vraiment pourquoi je devrais / ne devrais pas utiliser le modèle singleton. Je connais les problèmes que l'on peut avoir avec l'état global. Quoi qu'il en soit, le sujet de la question était de savoir comment avoir des objets uniques et pourtant une implémentation sans motif singleton.
Giorgio
3

La plupart des gens (y compris vous) comprennent complètement mal ce qu'est réellement le modèle Singleton. Le modèle Singleton signifie uniquement qu'une instance d'une classe existe et qu'il existe un mécanisme permettant au code dans toute l'application d'obtenir une référence à cette instance.

Dans le livre GoF, la méthode d'usine statique qui renvoie une référence à un champ statique n'était qu'un exemple à quoi ce mécanisme pourrait ressembler, et une avec de graves inconvénients. Malheureusement, tout le monde et leur chien se sont accrochés à ce mécanisme et ont pensé que c'était à cela que ressemblait Singleton.

Les alternatives que vous citez sont en fait aussi des singletons, juste avec un mécanisme différent pour obtenir la référence. (2) entraîne clairement trop de contournements, sauf si vous n'avez besoin de la référence qu'à quelques endroits près de la racine de la pile d'appels. (1) sonne comme un cadre d'injection de dépendance brute - alors pourquoi ne pas en utiliser un?

L'avantage est que les infrastructures DI existantes sont flexibles, puissantes et bien testées. Ils peuvent faire bien plus que gérer des Singletons. Cependant, pour utiliser pleinement leurs capacités, ils fonctionnent mieux si vous structurez votre application d'une certaine manière, ce qui n'est pas toujours possible: idéalement, il y a des objets centraux qui sont acquis via le framework DI et ont toutes leurs dépendances transitoirement peuplées et sont puis exécuté.

Edit: En fin de compte, tout dépend de la méthode principale de toute façon. Mais vous avez raison: la seule façon d'éviter complètement l'utilisation de global / statique est d'avoir tout configuré à partir de la méthode principale. Notez que DI est le plus populaire dans les environnements de serveur où la méthode principale est opaque et configure le serveur et le code d'application consiste essentiellement en rappels qui sont instanciés et appelés par le code du serveur. Mais la plupart des frameworks DI permettent également un accès direct à leur registre central, par exemple le ApplicationContext de Spring, pour des "cas spéciaux".

Donc, fondamentalement, la meilleure chose que les gens ont trouvée jusqu'à présent est une combinaison intelligente des deux alternatives que vous avez mentionnées.

Michael Borgwardt
la source
Voulez-vous dire que l'injection de dépendance d'idées de base est fondamentalement la première approche que j'ai esquissée ci-dessus (par exemple en utilisant un registre), enfin l'idée de base?
Giorgio
@Giorgio: DI inclut toujours un tel registre, mais le point principal qui le rend meilleur est qu'au lieu d'interroger le registre partout où vous avez besoin d'un objet, vous avez les objets centraux qui sont transitoirement peuplés, comme je l'ai écrit ci-dessus.
Michael Borgwardt
@Giorgio: C'est plus facile à comprendre avec un exemple concret: disons que vous avez une application Java EE. Votre logique frontale se trouve dans une méthode d'action d'un bean géré JSF. Lorsque l'utilisateur appuie sur un bouton, le conteneur JSF crée une instance du bean géré et appelle la méthode d'action. Mais avant cela, le composant DI examine les champs de la classe Bean managé, et ceux qui ont une certaine annotation sont remplis avec une référence à un objet Service selon la configuration DI, et ces objecs de service ont la même chose qui leur arrive . La méthode d'action peut alors appeler les services.
Michael Borgwardt
Certaines personnes (vous y compris) ne lisent pas attentivement les questions et comprennent mal ce qui est réellement demandé.
Giorgio
1
@Giorgio: rien dans votre question d'origine n'indiquait que vous connaissiez déjà l'injection de dépendance. Et voir la réponse mise à jour.
Michael Borgwardt
3

Il existe quelques alternatives:

Injection de dépendance

Chaque objet a ses dépendances qui lui sont transmises lors de sa création. En règle générale, un framework ou un petit nombre de classes d'usine sont responsables de la création et du câblage des objets

Registre des services

Chaque objet reçoit un objet de registre de service. Il fournit des méthodes pour demander différents objets différents qui fournissent différents services. Le cadre Android utilise ce modèle

Gestionnaire racine

Il existe un seul objet qui est la racine du graphe d'objet. En suivant les liens de cet objet, tout autre objet peut éventuellement être trouvé. Le code a tendance à ressembler à:

GetSomeManager()->GetRootManager()->GetAnotherManager()->DoActualThingICareAbout()
Winston Ewert
la source
Outre la moquerie, quel avantage DI a-t-il sur l'utilisation d'un singleton? C'est une question qui me dérange depuis très longtemps. Quant au registre et au gestionnaire racine, ne sont-ils pas implémentés en tant que singleton ou via DI? (En fait, le conteneur IoC est un registre, non?)
Konrad Rudolph
1
@KonradRudolph, avec DI les dépendances sont explicites, et l'objet ne fait aucune hypothèse sur la façon dont une ressource donnée est fournie. Avec les singletons, les dépendances sont implicites et chaque utilisation unique suppose qu'il n'y aura jamais qu'un seul objet de ce type.
Winston Ewert
@KonradRudolph, les autres méthodes sont-elles implémentées à l'aide de singletons ou DI? Oui et non. Au final, vous devez soit passer des objets dans, soit stocker des objets dans un état global. Mais il existe de subtiles différences dans la façon de procéder. Les trois versions mentionnées ci-dessus évitent toutes d'utiliser l'état global; mais la manière dont le code final obtient les références est différente.
Winston Ewert
L'utilisation de singletons ne suppose aucun objet unique si le singleton implémente une interface (ou même si ce n'est pas le cas) et que vous passez l'instance singleton aux méthodes, au lieu d'interroger Singleton::instancepartout dans le code client.
Konrad Rudolph
@KonradRudolph, alors vous utilisez vraiment une approche hybride Singleton / DI. Mon côté puriste pense que vous devriez opter pour une approche DI pure. Cependant, la plupart des problèmes avec singleton ne s'appliquent pas vraiment là-bas.
Winston Ewert
1

Si vos besoins du singleton peuvent se résumer à une seule fonction, pourquoi ne pas simplement utiliser une simple fonction d'usine? Les fonctions globales (probablement une méthode statique de classe F dans votre exemple) sont intrinsèquement singletons, avec unicité imposée par le compilateur et l'éditeur de liens.

class Factory
{
public:
    static Object* createObject(...);
};

Object* obj = Factory::createObject(...);

Certes, cela tombe en panne lorsque le fonctionnement du singleton ne peut pas être réduit à un seul appel de fonction, bien qu'un petit ensemble de fonctions connexes puisse peut-être vous aider.

Cela étant dit, les points 1 et 2 de votre question montrent clairement que vous ne voulez vraiment que quelque chose. Selon votre définition du motif singleton, vous l'utilisez déjà ou très proche. Je ne pense pas que vous puissiez avoir quelque chose sans que ce soit un singleton ou du moins très proche. C'est tout simplement trop proche de la signification de singleton.

Comme vous le suggérez, à un moment donné, vous devez avoir quelque chose, donc peut-être que le problème n'est pas d'avoir une seule instance de quelque chose, mais de prendre des mesures pour empêcher (ou au moins décourager ou minimiser) l'abus. Déplacer autant d'états que possible du "singleton" vers les paramètres est un bon début. Rétrécir l'interface du singleton est également utile, car il y a moins de possibilités d'abus de cette façon. Parfois, il suffit de le sucer et de rendre le singleton très robuste, comme le tas ou le système de fichiers.

Randall Cook
la source
4
Personnellement, je vois cela comme une implémentation différente du modèle singleton. Dans ce cas, l'instance singleton est la classe elle-même. Plus précisément: il présente tous les mêmes inconvénients qu'un "vrai" singleton.
Joachim Sauer
@Randall Cook: Je pense que votre réponse capture mon point. J'ai lu très souvent que le singleton est très très mauvais et doit être évité à tout prix. Je suis d'accord sur cela mais d'un autre côté je pense qu'il est techniquement impossible de toujours l'éviter. Lorsque vous avez besoin de "l'un de quelque chose", vous devez le créer quelque part et y accéder d'une manière ou d'une autre.
Giorgio
1

Si vous utilisez un langage multiparadigm comme C ++ ou Python, une alternative à une classe singleton est un ensemble de fonctions / variables enveloppées dans un espace de noms.

Conceptuellement parlant, un fichier C ++ avec des variables globales libres, des fonctions globales gratuites et des variables statiques utilisées pour cacher des informations, le tout enveloppé dans un espace de noms, vous donne presque le même effet qu'une "classe" singleton.

Il ne tombe en panne que si vous souhaitez hériter. J'ai vu beaucoup de singletons qui auraient été mieux ainsi.

Gort le robot
la source
0

Encapsulation de singletons

Scénario de cas. Votre application est orientée objet et nécessite 3 ou 4 singletons spécifiques, même si vous avez plus de classes.

Avant l'exemple (C ++ comme pseudocode):

// network info
class Session {
  // ...
};

// current user connection info
class CurrentUser {
  // ...
};

// configuration upon user
class UserConfiguration {
  // ...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfiguration {
  // ...
};

Une façon consiste à supprimer partiellement certains singletons (globaux), en les encapsulant tous dans un singleton unique, qui a pour membres, les autres singletons.

De cette façon, au lieu de "plusieurs singletons, approche, contre, pas de singleton du tout, approche", nous avons le "encapsuler tous les singletons en une seule, approche".

Après l'exemple (C ++ comme pseudocode):

// network info
class NetworkInfoClass {
  // ...
};

// current user connection info
class CurrentUserClass {
  // ...
};

// configuration upon user
// favorite options menues
class UserConfigurationClass {
  // ...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfigurationClass {
  // ...
};

// this class is instantiated as a singleton
class SessionClass {
  public: NetworkInfoClass NetworkInfo;
  public: CurrentUserClass CurrentUser;
  public: UserConfigurationClass UserConfiguration;
  public: TerminalConfigurationClass TerminalConfiguration;

  public: static SessionClass Instance();
};

Veuillez noter que les exemples ressemblent davantage à des pseudocodes et ignorent les bugs mineurs ou les erreurs de syntaxe et envisagent la solution à la question.

Il y a aussi autre chose à considérer, car le langage de programmation utilisé peut affecter la façon d'implémenter votre implémentation singleton ou non singleton.

À votre santé.

umlcat
la source
0

Vos exigences d'origine:

Donc, le scénario général est que

  1. Je n'ai besoin que d'une seule instance d'une classe soit pour des raisons d'optimisation (je n'ai pas besoin de> plusieurs objets d'usine) ou pour partager un état commun (par exemple, l'usine sait combien> d'instances de A elle peut encore créer)
  2. J'ai besoin d'un moyen d'avoir accès à cette instance f de F à différents endroits du code.

Ne vous alignez pas sur la définition d'un singleton (et sur ce à quoi vous faites plutôt référence plus tard). De GoF (ma version est 1995) page 127:

Assurez-vous qu'une classe ne possède qu'une seule instance et fournissez un point d'accès global à celle-ci.

Si vous n'avez besoin que d'une seule instance, cela ne vous empêche pas d'en créer d'autres.

Si vous voulez une seule instance accessible globalement, créez une seule instance globalement accessible . Il n'est pas nécessaire d'avoir un modèle nommé pour tout ce que vous faites. Et oui, les instances uniques accessibles globalement sont généralement mauvaises. Leur donner un nom ne les rend pas moins mauvais .

Pour éviter l'accessibilité globale, les approches courantes «créer une instance et la transmettre» ou «demander au propriétaire de deux objets de les coller ensemble» fonctionnent bien.

Telastyn
la source
0

Que diriez-vous d'utiliser le conteneur IoC? Avec une certaine réflexion, vous pouvez vous retrouver avec quelque chose comme:

Dependency.Resolve<IFoo>(); 

Vous devrez spécifier quelle implémentation IFooutiliser à un démarrage, mais il s'agit d'une configuration unique que vous pouvez facilement remplacer plus tard. La durée de vie d'une instance résolue peut être normalement contrôlée par un conteneur IoC.

La méthode statique singleton void pourrait être remplacée par:

Dependency.Resolve<IFoo>().DoSomething();

La méthode getter singleton statique pourrait être remplacée par:

var result = Dependency.Resolve<IFoo>().GetFoo();

L'exemple est pertinent pour .NET, mais je suis certain que des résultats similaires peuvent être obtenus dans d'autres langues.

CodeART
la source