J'utilise le moteur de jeu cocos2d-x pour créer des jeux. Le moteur utilise déjà beaucoup de singletons. Si quelqu'un l'utilise, il devrait en connaître quelques-unes:
Director
SimpleAudioEngine
SpriteFrameCache
TextureCache
EventDispatcher (was)
ArmatureDataManager
FileUtils
UserDefault
et beaucoup plus avec au total environ 16 classes. Vous pouvez trouver une liste similaire sur cette page: Objets singleton dans Cocos2d-html5 v3.0 Mais quand je veux écrire, je dois beaucoup plus de singletons:
PlayerData (score, lives, ...)
PlayerProgress (passed levels, stars)
LevelData (parameters per levels and level packs)
SocialConnection (Facebook and Twitter login, share, friend list, ...)
GameData (you may obtain some data from server to configure the game)
IAP (for in purchases)
Ads (for showing ads)
Analytics (for collecting some analytics)
EntityComponentSystemManager (mananges entity creation and manipulation)
Box2dManager (manages the physics world)
.....
Pourquoi je pense qu'ils devraient être singletons? Parce que j'en aurai besoin à des endroits très différents de mon jeu, et un accès partagé serait très pratique. En d'autres termes, je ne sais pas quoi les créer quelque part et passer des pointeurs vers toute mon architecture car ce sera très difficile. Aussi ce sont des sortes de choses dont je n’ai besoin que d’une seule. Dans tous les cas, j'en aurai besoin de plusieurs, je peux aussi utiliser un motif Multiton. Mais le pire, c’est que Singleton est le motif le plus critiqué à cause de:
- bad testability
- no inheritance available
- no lifetime control
- no explicit dependency chain
- global access (the same as global variables, actually)
- ....
Vous pouvez trouver quelques idées ici: https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons et https://stackoverflow.com/questions/4074154/when-should-the-singleton -pas-pas-être-utilisé-en-dehors-de-l'évidence
Donc, je pense que je fais quelque chose de mal. Je pense que mon code sent . :) Comment les développeurs de jeux plus expérimentés résolvent-ils ce problème d'architecture? Je veux vérifier, il est peut-être encore normal dans le développement de jeux d’avoir plus de 30 singletons , considérés comme ceux qui sont déjà dans le moteur de jeu.
J'ai pensé utiliser une façade singleton qui contiendra toutes les classes dont j'ai besoin, mais chacune d'elles ne serait pas déjà une singleton. Cela éliminera beaucoup de problèmes et je n’aurais qu’un seul singleton qui serait la façade elle-même. Mais dans ce cas, je vais avoir un autre problème de conception. La façade deviendra un OBJET DE DIEU. Je pense que ça sent aussi . Je ne peux donc pas trouver une bonne solution de conception pour cette situation. S'il vous plaît des conseils.
la source
Réponses:
J'initialise mes services dans ma classe d'applications principale, puis les passe en tant que pointeurs vers ceux qui doivent les utiliser, que ce soit par le biais des constructeurs ou des fonctions. Ceci est utile pour deux raisons.
Premièrement, l'ordre d'initialisation et de nettoyage est simple et clair. Il n’existe aucun moyen d’initialiser accidentellement un service ailleurs que vous ne le pouvez avec un singleton.
Deux, il est clair qui dépend de quoi. Je peux facilement voir quelles classes ont besoin de quels services juste à partir de la déclaration de classe.
Vous pensez peut-être ennuyeux de faire circuler tous ces objets, mais si le système est bien conçu, ce n'est vraiment pas mal du tout et rend les choses beaucoup plus claires (pour moi en tout cas).
la source
Je ne discuterai pas de la perversité des singletons car Internet peut le faire mieux que moi.
Dans mes jeux, j'utilise le modèle Service Locator pour éviter d'avoir des tonnes de singletons / gestionnaires.
le concept est assez simple. Vous n'avez qu'un seul Singleton qui agit comme la seule interface permettant d'atteindre ce que vous utilisiez auparavant comme Singleton. Au lieu d'avoir plusieurs Singleton, vous n'en avez plus qu'un.
Des appels comme:
Cela ressemble à ceci, en utilisant le modèle Service Locator:
Comme vous pouvez le constater, chaque singleton est contenu dans le localisateur de services.
Pour une explication plus détaillée, je vous suggère de lire cette page sur l’utilisation du modèle de localisateur .
[ EDIT ]: comme indiqué dans les commentaires, le site gameprogrammingpatterns.com contient également une section très intéressante sur Singleton .
J'espère que ça aide.
la source
La lecture de toutes ces réponses, commentaires et articles mis en évidence, en particulier ces deux brillants articles,
finalement, je suis arrivé à la conclusion suivante, qui est en quelque sorte une réponse à ma propre question. La meilleure approche consiste à ne pas être paresseux et à passer directement aux dépendances. Ceci est explicite, vérifiable, il n’ya pas d’accès global, peut indiquer une mauvaise conception si vous devez transmettre les délégués très en profondeur et à de nombreux endroits, etc. Mais dans la programmation, une taille unique ne convient pas à tous. Ainsi, il y a encore des cas où il n'est pas pratique de les passer directement. Par exemple, dans le cas où nous créons un cadre de jeu (un modèle de code qui sera réutilisé comme code de base pour développer différents types de jeux) et y incluons de nombreux services. Ici, nous ne savons pas à quelle profondeur chaque service peut être transmis et combien d'endroits il sera nécessaire. Cela dépend d'un jeu en particulier. Alors, que faisons-nous dans ce genre de cas? Nous utilisons le modèle de conception Service Locator au lieu de Singleton,
Nous devrions également considérer que ce modèle a été utilisé dans Unity, LibGDX, XNA. Ce n’est pas un avantage, mais c’est une sorte de preuve de la facilité d’utilisation du motif. Considérez que ces moteurs ont longtemps été développés par de nombreux développeurs intelligents et qu’ils ne trouvaient toujours pas de meilleure solution pendant les phases d’évolution du moteur.
En conclusion, je pense que Service Locator peut être très utile pour les scénarios décrits dans ma question, mais doit tout de même être évité si cela est possible. En règle générale, utilisez-le si nécessaire.
EDIT: Après une année de travail et d’utilisation directe de DI et de problèmes avec d’énormes constructeurs et de nombreuses délégations, j’ai effectué davantage de recherches et trouvé un bon article qui parle des avantages et des inconvénients des méthodes de DI et de localisation de service. Citant cet article ( http://www.martinfowler.com/articles/injection.html ) de Martin Fowler qui explique quand les localisateurs de service sont mauvais:
Mais de toute façon, si vous voulez nous voir pour la première fois, vous devriez lire cet article http://misko.hevery.com/2008/10/21/dependency-injection-myth-reference-passing/ (signalé par @megadan) , en accordant une grande attention à la loi de Demeter (LoD) ou au principe de moindre connaissance .
la source
Il n'est pas rare que des parties d'une base de code soient considérées comme des objets fondamentaux ou une classe de base, mais cela ne justifie pas que son cycle de vie soit dicté en tant que Singleton.
Les programmeurs utilisent souvent le modèle Singleton comme moyen de commodité et de pure paresse plutôt que d’adopter une approche alternative et d’être un peu plus verbeux et d’imposer des relations d’objet entre eux de manière explicite.
Alors demandez-vous ce qui est plus facile à maintenir.
Si vous êtes comme moi, je préfère pouvoir ouvrir un fichier d'en-tête et survoler brièvement les arguments du constructeur et les méthodes publiques pour voir les dépendances requises par un objet plutôt que de devoir inspecter des centaines de lignes de code dans un fichier d'implémentation.
Si vous avez créé un objet en Singleton et que vous réalisez plus tard que vous devez être capable de prendre en charge plusieurs instances de cet objet, imaginez les changements radicaux nécessaires si cette classe était quelque chose que vous utilisiez assez intensément dans votre base de code. La meilleure solution consiste à rendre explicites les dépendances, même si l'objet n'est alloué qu'une seule fois et qu'il est nécessaire de prendre en charge plusieurs instances, vous pouvez le faire en toute sécurité, sans ces inquiétudes.
Comme d'autres l'ont souligné, l'utilisation du modèle Singleton masque également le couplage, ce qui signifie souvent une conception médiocre et une grande cohésion entre les modules. Une telle cohésion est généralement indésirable et conduit à un code fragile qui peut être difficile à maintenir.
la source
Singleton est un modèle célèbre, mais il est bon de connaître ses objectifs, ses avantages et ses inconvénients.
C'est vraiment logique s'il n'y a pas de relation du tout.
Si vous pouvez gérer votre composant avec un objet totalement différent (sans dépendance forte) et espérer avoir le même comportement, le singleton peut être un bon choix.
D'autre part, si vous avez besoin d'une petite fonctionnalité , utilisée uniquement à certaines heures, vous devriez préférer passer des références .
Comme vous l'avez mentionné, les singletons n'ont aucun contrôle à vie. Mais l'avantage est qu'ils ont une initialisation paresseuse.
Si vous n'appelez pas ce singleton dans la durée de vie de votre application, il ne sera pas instancié. Mais je doute que vous construisiez des singletons sans les utiliser.
Vous devez voir un singleton comme un service au lieu d'un composant .
Singletons fonctionne, ils n'ont jamais cassé aucune application. Mais leurs lacunes (les quatre que vous avez données) sont des raisons pour lesquelles vous n'en voulez pas.
la source
La méconnaissance n'est pas la raison pour laquelle les singletons doivent être évités.
Singleton est nécessaire ou inévitable pour de nombreuses bonnes raisons. Les frameworks de jeu utilisent souvent des singletons car il s'agit d'une conséquence inévitable du fait de n'avoir qu'un seul matériel dynamique. Cela n'a aucun sens de vouloir jamais contrôler ces matériels avec plusieurs instances de leurs gestionnaires respectifs. La surface graphique est un matériel externe dynamique et initialiser accidentellement une seconde copie du sous-système graphique est tout à fait désastreux, car les deux sous-systèmes graphiques se disputeront la question de savoir qui dessine et quand, se écrasant de manière incontrôlable. De même avec le système de file d'attente d'événements, ils se disputeront l'identité des événements souris de manière non déterministe. Lorsque vous traitez avec un matériel externe avec état dans lequel il n'y en a qu'un, singleton est inévitable pour éviter les conflits.
Singleton est également sensible aux gestionnaires de cache. Les caches sont un cas particulier. Ils ne devraient pas vraiment être considérés comme des singleton, même lorsqu'ils utilisent toutes les mêmes techniques que les singletons pour rester en vie et vivre éternellement. Les services de cache sont des services transparents, ils ne sont pas censés modifier le comportement du programme. Par conséquent, si vous remplacez le service de cache par un cache nul, le programme devrait toujours fonctionner, à l'exception du fait qu'il s'exécute plus lentement. Cependant, la principale raison pour laquelle les gestionnaires de cache sont une exception pour singleton est qu’il n’est pas logique de fermer un service de cache avant d’appliquer l’application elle-même, car cela jettera également les objets en cache, ce qui l’évitera de l’avoir comme base de travail. singleton.
Ce sont de bonnes raisons pour lesquelles ces services sont des singletons. Cependant, aucune des bonnes raisons d’avoir des singletons ne s’applique aux cours que vous avez énumérés.
Ce ne sont pas des raisons pour singletons. Ce sont des raisons pour les globals. C'est également un signe de mauvaise conception si vous devez transmettre beaucoup de choses autour de divers systèmes. Le fait de devoir passer des objets indique un couplage élevé qui peut être empêché par une bonne conception OO.
En regardant la liste des cours de votre article qui, à votre avis, devraient être des singletons, je peux dire que la moitié d’entre eux ne devraient vraiment pas être des singletons et que l’autre moitié semble ne pas même être là au départ. Le fait que vous ayez besoin de passer des objets semble être dû au manque d’encapsulation appropriée plutôt qu’à de bons cas d’utilisation pour des singletons.
Regardons vos cours petit à petit:
Juste à partir des noms de classes, je dirais que ces classes sentent le modèle Anemic Domain Model . Les objets anémiques génèrent généralement beaucoup de données, ce qui augmente le couplage et alourdit le reste du code. De plus, anemic class cache le fait que vous pensez probablement toujours de manière procédurale plutôt que d'utiliser l'orientation objet pour encapsuler des détails.
Pourquoi ces cours doivent-ils être des singletons? Il me semble que ces classes devraient être éphémères, elles devraient être évoquées dès qu’elles sont nécessaires et déconstruites lorsque l’utilisateur termine l’achat ou lorsque les annonces ne doivent plus être diffusées.
En d'autres termes, un constructeur et une couche de service? Pourquoi cette classe est-elle sans limites claires ni but, même là au départ?
Pourquoi la progression du joueur est-elle séparée de la classe de joueur? La classe de joueurs doit savoir comment suivre ses propres progrès. Si vous souhaitez implémenter le suivi des progrès dans une classe différente de celle du joueur pour la séparation des responsabilités, alors PlayerProgress doit être derrière Player.
Je ne peux pas vraiment en dire plus sur celui-ci sans savoir ce que fait réellement ce cours, mais une chose est claire, c'est que ce cours est mal nommé.
Cela semble être les seules classes où Singleton peut être raisonnable, car les objets de connexion sociale ne sont pas réellement vos objets. Les connexions sociales ne sont que l'ombre d'objets externes qui résident dans un service distant. En d'autres termes, c'est une sorte de cache. Assurez-vous simplement de séparer clairement la partie mise en cache de la partie service du service externe, car cette dernière partie ne devrait pas nécessairement être un singleton.
Une façon d'éviter de transmettre des instances de ces classes consiste à utiliser le transfert de messages. Plutôt que d'appeler directement des instances de ces classes, vous envoyez un message de manière asynchrone au service Analytic et SocialConnection. Ces services sont abonnés pour recevoir ces messages et agir en conséquence. Étant donné que la file d'attente d'événements est déjà un singleton, vous évitez de passer une instance réelle lors de la communication avec un singleton.
la source
Les singletons pour moi sont TOUJOURS mauvais.
Dans mon architecture, j'ai créé une collection GameServices gérée par la classe de jeu. Je peux rechercher ajouter à et supprimer de cette collection si nécessaire.
En disant que quelque chose a une portée mondiale, vous dites essentiellement "cette chose est un dieu et n’a pas de maître" dans les exemples que vous avez donnés ci-dessus, je dirais que la plupart de ces cours sont des classes d’aides ou des GameServices, auquel cas le jeu en serait responsable.
Mais ce n'est que ma conception dans mon moteur, votre situation peut être différente.
Pour le dire plus directement ... Le seul vrai singleton est le jeu car il possède toutes les parties du code.
Cette réponse est basée sur la nature subjective de la question et est basée purement sur mon opinion, elle peut ne pas être correcte pour tous les développeurs.
Edit: comme demandé ...
Ok, ça vient de ma tête car je n'ai pas mon code devant moi pour le moment, mais la base de tout jeu est la classe Game, qui est vraiment un singleton ... ça doit être!
Alors j'ai ajouté ce qui suit ...
Maintenant, j'ai aussi une classe système (statique) qui contient tous mes singletons de tout le programme (contient très peu d'options de jeu et de configuration, et c'est à peu près ça) ...
Et l'utilisation ...
De partout je peux faire quelque chose comme
Cette approche signifie que mon jeu "possède" des éléments qui seraient généralement considérés comme un "singleton" et que je peux étendre la classe de base GameService comme je le souhaite. J'ai des interfaces comme IUpdateable dans lesquelles mon jeu recherche et appelle tous les services qui l'implémentent, au besoin dans des situations spécifiques.
J'ai également des services qui héritent / étendent d'autres services pour des scénarios de scènes plus spécifiques.
Par exemple ...
J'ai un service de physique qui gère mes simulations physiques, mais selon la scène, la simulation physique peut nécessiter un comportement légèrement différent.
la source
var tService = Sys.Game.getService<T>(); tService.Something();
au lieu de sauter lagetService
partie et d'y accéder directement?Un ingrédient essentiel de l’élimination du singleton que les adversaires du singleton omettent souvent de prendre en compte est l’ajout d’une logique supplémentaire pour gérer l’état beaucoup plus complexe que les objets encapsulent lorsque des singletons cèdent la place aux valeurs d’instance. En effet, même la définition de l'état d'un objet après le remplacement d'un singleton par une variable d'instance peut être difficile, mais les programmeurs qui remplacent des singletons par des variables d'instance doivent être prêts à le gérer.
Comparez, par exemple, Java
HashMap
avec .NETDictionary
. Les deux sont assez similaires, mais ils ont une différence essentielle: JavaHashMap
est codé en dur à utiliserequals
ethashCode
(il utilise efficacement un comparateur singleton) alors que .NETDictionary
peut accepter une instance de enIEqualityComparer<T>
tant que paramètre constructeur. Le coût d'exécution de la maintenanceIEqualityComparer
est minime, mais la complexité introduite ne l’est pas.Comme chaque
Dictionary
instance encapsule unIEqualityComparer
, son état n'est pas simplement un mappage entre les clés qu'elle contient réellement et leurs valeurs associées, mais plutôt un mappage entre les ensembles d'équivalence définis par les clés et le comparateur et leurs valeurs associées. Si deux instancesd1
etd2
desDictionary
références à la mêmeIEqualityComparer
, il sera possible de produire une nouvelle instanced3
telle que pour toutx
,d3.ContainsKey(x)
sera égald1.ContainsKey(x) || d2.ContainsKey(x)
. Si, toutefois,d1
etd2
peut contenir des références à différents comparateurs d'égalité arbitraires, il n'y aura généralement aucun moyen de les fusionner de manière à garantir que cette condition est valable.L'ajout de variables d'instance dans le but d'éliminer les singletons, mais le fait de ne pas prendre en compte correctement les nouveaux états possibles qui pourraient être impliqués par ces variables d'instance peut créer un code qui finit par être plus fragile que ce ne serait le cas avec un singleton. Si chaque instance d'objet possède un champ distinct, mais que les choses ne fonctionneront pas correctement si toutes ne identifient pas la même instance d'objet, tous les nouveaux champs achetés représentent un nombre croissant de façons dont les problèmes peuvent mal se produire.
la source