Quelle est la différence entre XNA Game Services et les variables globales glorifiées?

10

La Microsoft.Xna.Framework.Gameclasse a une propriété Services qui permet au programmeur d'ajouter un service à son jeu en fournissant le type de la classe et une instance de la classe à la méthode Add.

Maintenant, au lieu d'avoir à passer un AudioComponentà toutes les classes et méthodes qui en ont besoin, il vous suffit de passer votre Gameinstance et de rechercher le service. ( Localisateur de services )

Maintenant, parce que les jeux ont de nombreux services (GraphicsDevice, SceneGraph, AudioComponent, EffectsManager, et cetera), vous allez maintenant passer le jeu à tout.

Alors pourquoi ne pas simplement faire de ces instances un singleton? Eh bien, parce que les singletons sont mauvais parce qu'ils ont un état global, empêchent les tests et rendent votre conception beaucoup plus fragile. Les localisateurs de services sont également considérés comme un anti-modèle pour beaucoup car au lieu de simplement transmettre la dépendance à un objet, vous passez un localisateur de services (le jeu) qui couple cette classe avec le reste des services.

Alors pourquoi les services sont-ils recommandés dans XNA et le développement de jeux? Est-ce parce que les jeux sont différents des programmes réguliers et sont étroitement liés à leurs composants et devoir passer tous les composants nécessaires au fonctionnement d'une classe serait très lourd? Les services de jeux sont-ils alors un mal nécessaire dans la conception de jeux? Existe-t-il des alternatives qui n'impliquent pas de longues listes de paramètres et de couplage?

John Pollick
la source
3
"Les localisateurs de services sont également considérés comme un anti-modèle pour beaucoup" - [citation nécessaire]. De toute évidence, les utiliser là où vous n'en avez pas besoin est mauvais - c'est vrai pour toutes les constructions logicielles - mais je n'ai jamais entendu de localisateurs de services appelés anti-modèle. (D'un autre côté, j'essaie également d'éviter aux programmeurs qui passent la plupart de leur temps à discuter des dernières minuties de motifs.)
@JoeWreschnig bien que je sois d'accord avec vous, j'ai fait quelques recherches et trouvé ceci: blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx
Roy T.
2
Oui, mais ce type vend un livre sur l'utilisation de la DI; évidemment, il ne veut pas que vous utilisiez quelque chose de simple, alors vous n'achèterez pas son livre! (Sérieusement, l'article original DI / SL de Fowler couvrait ce sujet exact - martinfowler.com/articles/… - et le blog de ce type n'ajoute rien à la discussion.)
J'aimerais pouvoir marquer ce lien que vous venez de poster de Martin Fowler comme réponse (bien que ce ne soit pas ma question)
Roy T.

Réponses:

6

Je ne pense pas que tout le monde convienne que les localisateurs de services sont mauvais. Les localisateurs de services permettent de découpler des fonctionnalités importantes dont tout le monde a besoin de leur mise en œuvre réelle. Les localisateurs de services permettent également d'échanger des services au moment de l'exécution. De plus, je ne pense pas qu'il soit vrai que vous devez faire circuler le localisateur de services partout. Je pense que vous pouvez y accéder de partout. Alors qu'une classe de jeu avec des globaux devrait être transmise environ mille fois par image (ce qui entraînerait de mauvaises performances).

Pour faire la différence entre le localisateur de service et une classe avec des globaux un peu plus clair, considérons l'exemple suivant:

Je fais un jeu et j'utilise un objet de classe gameA qui a une variable globale: le chargeur de contenu. Maintenant, dans mon prochain jeu, je ne veux pas utiliser la classe gameA mais créer une nouvelle classe gameB. Je dois maintenant ajouter à nouveau les globaux de gameA à gameB qui est un travail supplémentaire. Il devient également difficile de configurer le type de chargeur de contenu que je souhaite. Disons que j'ai un jeu qui fonctionne à la fois sur PC et sur XBOX. Sur le PC, je veux un chargeur de contenu qui peut également ouvrir une boîte de dialogue «Ouvrir un fichier». Alors que sur la XBOX, je ne veux vraiment pas cela. En ayant un fichier de configuration, je peux même à mi-chemin dans le jeu changer facilement le chargeur de contenu, même ailleurs que dans la classe gameA, sans simplement rendre les globaux de gameA tous publics.

Considérez maintenant que j'utilise un localisateur de services. Je peux toujours continuer à l'utiliser. Et même le type de services peut être différent à tout moment. Il est également vrai que je ne me soucierai même pas de leur mise en œuvre tant qu'ils auront une interface commune. (Bien que je puisse également l'utiliser dans une classe de type gamA).

Quoi qu'il en soit, je pense que vous trouverez les services de jeux assez pratiques. Tout le monde que je connais l'utilise. Pour vous aider un peu plus. J'ai créé une petite classe d'aide autour des services de jeu afin que vous puissiez faire un peu moins de casting tout le temps: http://roy-t.nl/index.php/2010/08/25/xna-accessing-contentmanager -et-graphicsdevice-Anywhere-Anytime-the-Gameservicecontainer / Je l'utilise beaucoup.

Roy T.
la source
Merci pour la réponse. Dans une classe qui nécessiterait un service, demandez-vous généralement la classe GameServices pour le service chaque fois que vous en avez besoin, ou créez-vous une variable membre dans la classe et demandez-vous le service uniquement dans le constructeur? Je suppose que ce que je demande, c'est à quel point c'est lent return (T)Instance.GetService(typeof(T));.
John Pollick
Aussi, que pensez-vous de cette implémentation de la classe GameServices? J'ai supprimé le singleton inutile car il n'avait aucune fonctionnalité que l'abstraction n'avait pas déjà. De plus, j'ai supprimé le Servicesuffixe redondant à la fin de chaque méthode.
John Pollick
De plus, je pense que la raison pour laquelle la AddServiceméthode Services prend un type est que vous puissiez spécifier l'interface du service. Par exemple Services.AddService(typeof(IGraphicsDeviceManager), graphics);,. Ensuite, lors de la récupération du service, vous pouvez spécifier l'interface comme type et la caster dans la sous-classe choisie.
John Pollick
@JohnPollick, pour la première question. J'essaie de toujours le demander (car il aurait pu changer) mais si j'ai vraiment besoin de quelque chose à chaque trame, je crée une variable membre. Je n'ai pas beaucoup de statistiques sur les performances, sinon que cela n'a jamais été un goulot d'étranglement pour moi. La seconde, oui ça a l'air bien :). Et pour le troisième. C'est en effet la raison de l'argument type, mais vous ne devriez jamais prendre l'habitude de downcasting des valeurs récupérées car cela détruit votre capacité à échanger des classes en tant que service. Si vous ne voulez pas un IGraphicsDeviceManger mais un MyGDM alors créez éventuellement une nouvelle interface.
Roy T.20
1
Oh, je ne vous ai pas vu casté aussi. Bien sûr, il est capable de déduire le type si vous lancez le premier :).
Roy T.20
5

Alors pourquoi ne pas simplement faire de ces instances un singleton? Eh bien, parce que les singletons sont mauvais parce qu'ils ont un état global, empêchent les tests et rendent votre conception beaucoup plus fragile.

Les singletons prennent généralement beaucoup de traits utiles et les regroupent dans un horrible hybride qui vous donne plusieurs choses que vous ne voulez pas ainsi que le bit que vous vouliez. (S'agissait-il de caractéristiques de «création à la demande»? Portée mondiale? Application d'au moins une instance? Interdiction de plusieurs instances?)

Les localisateurs de services sont également considérés comme un anti-modèle pour beaucoup car au lieu de simplement transmettre la dépendance à un objet, vous passez un localisateur de services (le jeu) qui couple cette classe avec le reste des services.

Vous ne pouvez pas éviter complètement le couplage. Les choses utilisent d'autres choses et sans cela, votre programme ne fait rien. La seule vraie question est donc de savoir à quel niveau d'abstraction elle se situe.

La plupart des textes sur l'orientation des objets font une distinction entre la composition et l'agrégation. Il est important de connaître la différence conceptuelle entre quelque chose que vous possédez et quelque chose que vous connaissez. Malheureusement, la plupart des langues modernes n'ont pas de distinction claire ici, et une référence à un objet pourrait signifier l'une ou l'autre de ces choses. (Ironiquement, C ++ fait un meilleur travail, si vous utilisez les différents types de pointeurs intelligents.)

Vous pouvez rendre la composition et l'agrégation claires dans votre code en utilisant des services pour les objets agrégés. Les services sont un moyen de donner accès à des choses que vous ne possédez pas, mais que vous pouvez utiliser.

Alors pourquoi les services sont-ils recommandés dans XNA et le développement de jeux?

Je ne pense pas qu'ils soient universellement recommandés dans le développement de jeux.

Kylotan
la source