web-dev-qa-db-fra.com

Alternatives au modèle singleton

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é. Donc, 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 suis pas pas intéressé par la discussion si ce modèle est bon ou mauvais, mais en supposant que je veux é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é lors du 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 gérés directement ou indirectement.

Cette dernière option (utiliser 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 seul objet instance 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, récemment, 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 le seul singleton-pattern solution gratuite.

27
Giorgio

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 l'origine 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.

8
Caleb

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; en pratique, c'est rarement le cas cependant, et le multi-traitement et le multi-threading brouillent 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 ex. pour une usine: les classes d'usine typiques ont un minimum d'état, le cas échéant, et il n'y a aucune raison réelle de les maintenir en vie après 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 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.

18
tdammers

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 était juste un exemple à quoi ce mécanisme pourrait ressembler, et une avec sévère désavantages. 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 brut - 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, elles 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 dont toutes les dépendances sont transitoirement peuplées et sont puis exécuté.

Edit: En fin de compte, tout dépend de toute façon de la méthode principale. 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 code du serveur et de l'application se compose essentiellement de rappels qui sont instanciés et appelés par le code du serveur. Mais la plupart des cadres DI permettent également un accès direct à leur registre central, par exemple ApplicationContext de Spring, pour les "cas spéciaux".

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

4

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()
3
Winston Ewert

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.

1
Gort the Robot

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 des 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 indiquent 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, alors 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 du moins décourager ou minimiser) l'abus. Déplacer autant d'états du "singleton" dans les paramètres que possible 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.

1
Randall Cook

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 des "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 bogues 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é.

0
umlcat

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 l'implémentation de IFoo à utiliser au 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 s'applique à .NET, mais je suis certain que des résultats similaires peuvent être obtenus dans d'autres langues.

0
CodeART

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 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 avec la définition d'un singleton (et ce que vous ferez 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 n'empêche pas d'en créer d'autres.

Si vous voulez une seule instance accessible globalement, alors 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.

0
Telastyn