Comment puis-je éviter d'avoir plusieurs singletons dans mon architecture de jeu?

55

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.

Narek
la source
26
La plupart des gens qui se disputent contre Singleton ne semblent pas se rendre compte que le travail requis pour propager des données / fonctionnalités à travers tout le code pourrait être davantage une source de bogue que l’utilisation d’un Singleton. Donc fondamentalement, ils ne comparent pas, ils rejettent simplement ==> je soupçonne que l'anti-singletonisme est une posture :-) Si bien, Singleton a des défauts, donc si vous pouvez l'éviter, faites-le afin d'éviter les problèmes qui vont avec. Maintenant, si vous ne pouvez pas, allez-y sans regarder en arrière. Après tout, Cocos2d semble fonctionner assez bien avec ses 16 singletons ...
GameAlchemist
6
Pour rappel, il s'agit d'une question de savoir comment effectuer une tâche particulière. Ce n'est pas une question demandant une discussion pro / contre de singletons (qui serait hors sujet ici de toute façon). En outre, rappelez-vous que les commentaires doivent être utilisés pour demander des éclaircissements au demandeur, et non comme une plate-forme pour une discussion tangentielle sur les avantages et les inconvénients des singletons. Si vous souhaitez apporter votre contribution, la manière appropriée de le faire est de fournir une réponse légitime à la question , dans laquelle vous pouvez moduler votre solution avec des conseils pour ou contre le modèle de singleton.
Josh
Je suppose que ces singletons sont alors globalement accessibles? Ce qui serait une raison d'éviter ce schéma.
Peter - Réintégrer Monica

Réponses:

41

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).

mégadan
la source
34
+1 En outre, le "désagrément" de devoir passer des références aux systèmes environnants aide à contrecarrer le fluage de la dépendance; si vous devez passer quelque chose à " tout ", c'est un bon signe que vous avez un mauvais problème de couplage dans votre conception, et c'est beaucoup plus explicite de cette façon qu'avec un accès mondial promiscuité à tout ce qui se trouve de partout.
Josh
2
Cela ne fournit pas vraiment de solution ... vous avez donc votre classe de programmes avec un tas d'objets singleton et maintenant 10 niveaux de code, une partie de la scène a besoin de l'un de ces singletons ... comment est-il passé?
Guerre du
Wardy: Pourquoi auraient-ils besoin d'être singletons? Bien sûr, la plupart du temps, vous passez peut-être autour d'une instance particulière, mais ce qui fait qu'un singleton est un singleton, c'est que vous NE POUVEZ PAS utiliser une instance différente. Avec l'injection, vous pouvez passer une instance pour un objet et une autre pour la suivante. Ce n'est pas parce que vous ne le faites pas pour une raison quelconque que vous ne pouvez pas le faire.
Parar
3
Ce ne sont pas des singletons. Ce sont des objets normaux comme n'importe quoi d'autre avec un propriétaire bien défini qui contrôle init / cleanup. Si vous avez 10 niveaux de profondeur sans accès, vous avez peut-être un problème de conception. Tout ne nécessite pas ou ne devrait pas avoir accès aux moteurs de rendu, audio, etc. De cette façon, vous pensez à votre conception. En tant que bonus supplémentaire pour ceux qui testent leur code (quoi?), Le test unitaire devient également beaucoup plus facile.
megadan
2
Oui, je pense que l'injection de dépendance (avec ou sans framework comme Spring) est une solution parfaite pour gérer cela. Je trouve que cela est un grand article pour ceux qui se demandent comment mieux le code de structure pour éviter de passer partout les choses: misko.hevery.com/2008/10/21/...
megadan
17

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:

FlyingToasterManager.GetInstance().Toast();
SmokeAlarmManager.GetInstance().Start();

Cela ressemble à ceci, en utilisant le modèle Service Locator:

SvcLocator.FlyingToaster.Toast();
SvcLocator.SmokeAlarm.Start();

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.

Ivictorino
la source
4
Le site lié à ici, gameprogrammingpatterns.com, contient également un chapitre sur les singletons qui semble pertinent.
ajp15243
28
Les localisateurs de services sont simplement des singletons qui déplacent tous les problèmes normaux vers l'exécution plutôt que vers la compilation. Cependant, ce que vous suggérez n’est qu’un registre statique géant, qui est presque meilleur, mais qui reste essentiellement une masse de variables globales. Il s’agit simplement d’une mauvaise architecture si plusieurs niveaux ont besoin d’accéder aux mêmes objets. Une injection de dépendance correcte est ce qui est nécessaire ici, pas des globals nommés différemment par rapport aux globals actuels.
Magus
2
Pouvez-vous élaborer sur "l'injection de dépendance appropriée"?
Blue Wizard
17
Cela n'aide pas vraiment le problème. C'est essentiellement beaucoup de singletons fusionnés dans un singleton géant. Aucun des problèmes structurels sous-jacents n'est résolu ni même résolu, le code pue toujours plus joli maintenant.
ClassicThunder
4
@classicthunder Bien que cela ne règle pas tous les problèmes, il s'agit d'une grande amélioration par rapport aux singletons car il en règle plusieurs. En particulier, le code que vous écrivez n'est plus couplé à une seule classe, mais plutôt à une interface / catégorie de classes. Maintenant, vous pouvez facilement échanger différentes implémentations des différents services.
Jhocking le
12

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,

  1. Vous ne programmez pas pour les implémentations mais pour les interfaces. Ceci est essentiel pour les principes de la programmation orientée objet. Au moment de l'exécution, vous pouvez modifier ou même désactiver le service à l'aide du modèle d'objet nul. Ce n'est pas seulement important en termes de flexibilité, mais cela aide directement dans les tests. Vous pouvez désactiver, vous pouvez également utiliser le modèle Decorator, pour ajouter des fonctionnalités de journalisation et autres. C'est une propriété très importante que Singleton n'a pas.
  2. Un autre avantage énorme est que vous pouvez contrôler la durée de vie de l'objet. En Singleton, vous ne contrôlez que l'heure à laquelle l'objet sera créé par le modèle d'initialisation différée. Mais une fois que l'objet est créé, il vivra jusqu'à la fin du programme. Mais dans certains cas, vous souhaitez modifier le service. Ou même le fermer. Surtout dans les jeux, cette flexibilité est très importante.
  3. Il combine / encapsule plusieurs singletons dans une API compacte. Je pense que vous pouvez également utiliser certaines façades comme les localisateurs de services, qui, pour une même opération, peuvent utiliser plusieurs services à la fois.
  4. Vous pouvez faire valoir que nous avons toujours l'accès global, qui est le principal problème de conception du Singleton. Je suis d’accord, mais nous n’aurons besoin de ce schéma que si nous voulons un accès global. Nous ne devrions pas nous plaindre de l'accès global, si nous pensons que l'injection de dépendance directe n'est pas pratique pour notre situation et que nous avons besoin d'un accès mondial. Mais même pour ce problème, une amélioration est disponible (voir le deuxième article ci-dessus). Vous pouvez rendre tous les services privés et hériter de la classe Service Locator toutes les classes dont vous pensez qu’ils devraient utiliser les services. Cela peut considérablement réduire l'accès. Et considérez que dans le cas de Singleton, vous ne pouvez pas utiliser l'héritage à cause du constructeur privé.

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:

Le problème principal concerne donc les personnes qui écrivent du code qui est censé être utilisé dans des applications en dehors du contrôle de l'auteur. Dans ces cas, même une hypothèse minimale sur un localisateur de service pose problème.

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 .

Narek
la source
Mon intolérance personnelle envers les localisateurs de services découle principalement de mes tentatives pour tester le code qui les utilise. Dans l'intérêt du test de la boîte noire, vous appelez le constructeur pour créer une instance à tester. Une exception peut être levée immédiatement, ou à tout appel de méthode, et il se peut qu'aucune propriété publique ne vous conduise aux dépendances manquantes. Cela peut être mineur si vous avez accès à la source, mais cela viole toujours le principe de moindre surprise (un problème fréquent avec les singletons). Vous avez suggéré de passer au localisateur de services, ce qui atténue une partie de ce problème, et vous devez le féliciter.
Magus
3

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.

Naros
la source
-1: Cette réponse décrit à tort le motif singleton et tous ses arguments décrivent les problèmes posés par une mauvaise mise en œuvre du motif. Le singleton approprié est une interface et évite tous les problèmes que vous avez décrits (y compris le passage à la non-singularité, qui est un scénario que le GoF adresse explicitement). S'il est difficile de refactoriser l'utilisation d'un singleton, la refactorisation de l'utilisation d'une référence d'objet explicite ne peut être que plus difficile, pas moins. Si singleton provoque en quelque sorte un couplage de code MORE, cela est tout simplement incorrect.
Seth Battin
1

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.

Crazyrems
la source
1

Le moteur utilise déjà beaucoup de singletons. Si quelqu'un l'utilise, il devrait en connaître quelques-unes:

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.

Parce que j'en aurai besoin à des endroits très différents de mon jeu, et un accès partagé serait très pratique.

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:


PlayerData (score, lives, ...)
LevelData (parameters per levels and level packs)
GameData (you may obtain some data from server to configure the game)

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.


IAP (for in purchases)
Ads (for showing ads)

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.


EntityComponentSystemManager (mananges entity creation and manipulation)

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?


PlayerProgress (passed levels, stars)

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.


Box2dManager (manages the physics world)

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é.


Analytics (for collecting some analytics)
SocialConnection (Facebook and Twitter login, share, friend list, ...)

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.

Lie Ryan
la source
-1

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 ...

class Game
{
   IList<GameService> Services;

   public T GetService<T>() where T : GameService
   {
       return Services.FirstOrDefatult(s => s is T);
   }
}

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) ...

public static class Sys
{
    // only the main game assembly can set this (done by the game ctor)
    // anything can get
    public static Game Game { get; internal set; }
}

Et l'utilisation ...

De partout je peux faire quelque chose comme

var tService = Sys.Game.getService<T>();
tService.Something();

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.

Guerre
la source
1
Les services de jeu ne doivent-ils pas être instanciés une seule fois? Si c'est le cas, il s'agit simplement d'un modèle de singleton non imposé au niveau de la classe, ce qui est pire qu'un singleton correctement implémenté.
GameAlchemist
2
@Wardy Je ne comprends pas bien ce que vous avez fait mais cela ressemble à la façade de Singleton que j'ai décrite et cela devrait devenir un objet de DIEU, n'est-ce pas?
Narek
10
C'est juste un modèle Singleton implémenté au niveau du jeu. L’aspect le plus intéressant est donc de vous permettre de prétendre que vous n’utilisez pas Singleton. Utile si votre patron est de l'église anti-modèle.
GameAlchemist
2
Je ne suis pas sûr que ce soit vraiment différent de l'utilisation de Singletons. La seule différence dans la manière dont vous accédez spécifiquement à votre seul service existant de ce type? C'est-à-dire var tService = Sys.Game.getService<T>(); tService.Something();au lieu de sauter la getServicepartie et d'y accéder directement?
Christian
1
@Christian: En effet, c'est exactement ce qu'est l'antipattern de Service Locator. Apparemment, cette communauté pense toujours que c'est un modèle de conception recommandé.
Magus
-1

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 HashMapavec .NET Dictionary. Les deux sont assez similaires, mais ils ont une différence essentielle: Java HashMapest codé en dur à utiliser equalset hashCode(il utilise efficacement un comparateur singleton) alors que .NET Dictionarypeut accepter une instance de en IEqualityComparer<T>tant que paramètre constructeur. Le coût d'exécution de la maintenance IEqualityComparerest minime, mais la complexité introduite ne l’est pas.

Comme chaque Dictionaryinstance encapsule un IEqualityComparer, 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 instances d1et d2des Dictionaryréférences à la même IEqualityComparer, il sera possible de produire une nouvelle instance d3telle que pour tout x, d3.ContainsKey(x)sera égal d1.ContainsKey(x) || d2.ContainsKey(x). Si, toutefois, d1et d2peut 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.

supercat
la source
1
Bien qu’il s’agisse d’un aspect intéressant de la discussion, je ne pense pas qu’il aborde la question posée par le PO.
Josh
1
@JoshPetrie: Les commentaires sur le post original (par exemple, de Magus) suggéraient que le coût d'exécution des références pour éviter d'utiliser des singletons n'était pas énorme. En tant que tel, j’aimerais considérer comme pertinents les coûts sémantiques liés au fait que chaque objet encapsule des références dans le but d’éviter les singletons. Il faut éviter les singletons dans les cas où ils peuvent être évités facilement et à moindre coût, mais un code qui n'exige pas ouvertement un singleton, mais risque de se rompre dès que plusieurs occurrences de quelque chose existe est pire que le code qui utilise un singleton.
Supercat
2
Les réponses ne servent pas à adresser les commentaires mais à répondre directement à la question.
ClassicThunder
@ClassicThunder: Aimez-vous mieux l'édition?
Supercat