Alternatives aux singletons / globales

16

J'ai entendu d'innombrables fois les pièges des singletons / mondiaux, et je comprends pourquoi ils sont si souvent désapprouvés.

Ce que je ne comprends pas, c'est quelle est l'alternative élégante et non salissante. Il semble que l'alternative à l'utilisation de singletons / globaux implique toujours de faire passer des objets un million de niveaux à travers les objets de votre moteur jusqu'à ce qu'ils atteignent les objets qui en ont besoin.

Par exemple, dans mon jeu, je précharge certains éléments au démarrage du jeu. Ces ressources ne sont utilisées que bien plus tard lorsque le joueur navigue dans le menu principal et entre dans le jeu. Suis-je censé transmettre ces données de mon objet Game, à mon objet ScreenManager (malgré le fait qu'un seul écran se soucie réellement de ces données), puis à l'objet Screen approprié, et ailleurs?

Il semble juste que j'échange des données d'état globales pour une injection de dépendance encombrée, en passant des données à des objets qui ne se soucient même pas des données, sauf dans le but de les transmettre à des objets enfants.

Est-ce un cas où un Singleton serait une bonne chose, ou y a-t-il une solution élégante qui me manque?

vargonian
la source

Réponses:

16

Ne confondez pas singletons et globales. Alors que certains types de variables globales sont généralement nécessaires, le singleton n'est pas seulement un remplacement pour une variable globale, mais principalement un moyen de contourner les problèmes d'ordre d'initialisation statique en C ++ ( et FQA ). (Dans d'autres langues, c'est un moyen de contourner différentes déficiences de langage, comme le manque de variables globales et de fonctions nues.)

Si vous pouvez simplement utiliser un pointeur global au lieu d'un singleton et vous assurer qu'il est initialisé (manuellement) avant que quoi que ce soit en ait besoin, vous évitez l'appel de fonction et la surcharge de branche, la syntaxe boiteuse pour atteindre l'objet et vous pouvez réellement faire un deuxième instance de la classe lorsque vous en avez besoin pour des tests ou parce que votre conception a changé.

Pour les quelques variables globales que vous souhaitez (des exemples courants étant la sortie audio, la liste des fenêtres ouvertes, le gestionnaire de clavier, etc.), je recommande le modèle de localisateur de service . Il permet de remplacer facilement les choses par différentes implémentations (par exemple, un périphérique audio réel ou nul) et rassemble tous vos globaux dans une seule structure pour éviter de polluer votre espace de noms.


la source
+1. Bonne réponse et merci pour le lien du modèle de localisation de service. C'est une lecture très intéressante.
bummzack
1

Si vous ne pouvez pas / ne pouvez pas avoir une partie du code comme par «connaissance» magique de certaines données, elles devront être transmises d'une manière ou d'une autre. Cependant, cela ne signifie pas qu'il doit nécessairement être passé uniquement par des arguments.

Dans votre exemple, ne pourriez-vous pas avoir une sorte de "AssetManager" qui chargerait et stockerait les actifs, et le ScreenManager n'aurait alors besoin que d'une référence à cela (probablement lors de la création)? En ce sens, vous transmettez les références aux actifs enveloppés dans un autre objet, et vous pouvez les transmettre une fois, lors de l'initialisation, plutôt que de les transmettre à la fonction feuille lorsque cela est nécessaire.

Maintenant à mon humble avis que AssetManager, étant le genre de chose dont vous ne voulez qu'une, pourrait tout aussi bien être un singleton. À condition de comprendre les pièges et de coder spécifiquement pour les éviter (supposez que le singleton sera accessible simultanément à partir de plusieurs threads et vous poignarder avec une fourchette chaque fois que vous faites quelque chose qui doit bloquer), puis assommez-vous.

JasonD
la source
1

Je pense que Jason D a tout à fait raison - c'est ainsi que je gérerais cela:

Le jeu a une instance de AssetManager, un objet à partir duquel vous pouvez obtenir n'importe quel actif par son nom.

En jeu:

assetManager = new AssetManager();
screenManager = new ScreenManager();
screenManager.assetManager = assetManager;

Dans ScreenManager:

screen = new Screen();
screen.assetManager = assetManager;

À l'écran:

myAsset = assetManager.getBitmp("lava.png");

Tous les écrans ont désormais accès aux ressources dont ils ont besoin. Ce n'est pas plus complexe ou fou que d'utiliser des globales ou des singletons, et vous avez la possibilité d'avoir 2 instances de jeu en cours d'exécution dans la même application sans heurts. J'ai dû créer un jeu composé de 8 mini-jeux, partageant tous les mêmes classes de base / framework. J'ai dû refactoriser tous mes globals / singletons pour utiliser ce style de passage de référence, et je n'ai jamais regardé en arrière. Les seules choses qui devraient être globales sont des choses qui ne peuvent exister physiquement qu'une seule fois, comme l'audio, la mise en réseau, les E / S, etc.

Iain
la source
0

Vous pouvez utiliser le modèle Factory pour remplacer Singleton . Ensuite, la classe d'usine contrôle le nombre d'instances que vous pouvez créer, que vous pouvez facilement modifier plus tard lorsque vous en avez besoin de plusieurs AssetManager. Comme indiqué dans cet article :

Il vous donne toute la flexibilité du Singleton, avec loin autant de problèmes.


Une autre possibilité, assez limitée, est de rendre la classe statique (ce qui, je pense, n'est pas faisable pour un AssetManager et n'est possible que dans les langages qui ont des classes statiques). Mais cela ne fonctionne que si vous n'avez pas besoin d'héritage / polymorphisme. C'est une solution très rigide:

les méthodes statiques sont aussi flexibles que le granit. Chaque fois que vous en utilisez un, vous moulez une partie de votre programme en béton. Assurez-vous simplement de ne pas coincer votre pied là-dedans pendant que vous le regardez durcir. Un jour, vous serez étonné que, par Dieu, vous ayez vraiment besoin d'une autre implémentation de cette classe dang PrintSpooler, et cela aurait dû être une interface, une fabrique et un ensemble de classes d'implémentation. Oh!

Il s'agit de méthodes statiques, mais elle peut également être appliquée à des classes statiques.

Michael Klement
la source
Qu'entendez-vous par «rendre la classe statique»? C ++ n'a pas de classes statiques. Voulez-vous dire que vous n'avez que des méthodes statiques? Pourquoi s'embêter à avoir une classe au lieu d'un espace de noms alors?
1
@Joe: Eh bien, la question n'est pas centrée sur le C ++ tel que je l'ai compris. En C # ou Java, vous pouvez créer des classes statiques et j'y fais référence. De plus, comme je l'ai dit, les classes statiques ne sont pas une solution optimale la plupart du temps, mais dans de rares cas, cela pourrait fonctionner comme un global.
Michael Klement