El motor ya usa muchos singletons. Si alguien lo usó, entonces debería estar familiarizado con algunos de ellos:
La falta de familiaridad no es la razón por la cual se deben evitar los singletons.
Hay muchas buenas razones por las que Singleton es necesario o inevitable. Los marcos de juego a menudo usan singletons porque es una consecuencia inevitable de tener un solo hardware con estado. No tiene sentido querer controlar estos hardwares con múltiples instancias de sus respectivos controladores. La superficie gráfica es un hardware externo con estado y accidentalmente inicializar una segunda copia del subsistema gráfico no es nada desastroso, ya que ahora los dos subsistemas gráficos se enfrentarán entre sí por quién dibuja y cuándo, sobrescribiéndose sin control. Del mismo modo, con el sistema de cola de eventos, se pelearán por quién obtiene los eventos del mouse de manera no determinista. Cuando se trata de un hardware externo con estado en el que solo hay uno de ellos, singleton es inevitable para evitar conflictos.
Otro lugar donde Singleton es sensible es con los administradores de caché. Los cachés son un caso especial. Realmente no deberían considerarse un Singleton, incluso cuando usan las mismas técnicas que los Singleton para mantenerse con vida y vivir para siempre. Los servicios de almacenamiento en caché son servicios transparentes, no se supone que alteren el comportamiento del programa, por lo que si reemplaza el servicio de caché con un caché nulo, el programa aún debería funcionar, excepto que solo se ejecuta más lentamente. Sin embargo, la razón principal por la que los administradores de caché son una excepción a singleton es porque no tiene sentido cerrar un servicio de caché antes de cerrar la aplicación en sí, porque eso también descartará los objetos en caché, lo que frustra el punto de tenerlo como un semifallo.
Esas son buenas razones por las cuales estos servicios son únicos. Sin embargo, ninguna de las buenas razones para que los singletons se apliquen a las clases que ha enumerado.
Porque los necesitaré en lugares muy diferentes en mi juego, y el acceso compartido sería muy útil.
Esas no son razones para solteros. Esas son razones para los globales. También es una señal de mal diseño si necesita pasar muchas cosas por varios sistemas. Tener que pasar cosas indica un alto acoplamiento que puede evitarse con un buen diseño OO.
Solo por mirar la lista de clases en tu publicación que crees que deben ser Singletons, puedo decir que la mitad de ellos realmente no deberían ser Singletons y la otra mitad parece que ni siquiera deberían estar allí en primer lugar. El hecho de que necesite pasar objetos parece deberse a la falta de una encapsulación adecuada en lugar de a los casos de buen uso reales para singletons.
Veamos tus clases poco a poco:
PlayerData (score, lives, ...)
LevelData (parameters per levels and level packs)
GameData (you may obtain some data from server to configure the game)
Solo por los nombres de las clases, diría que estas clases huelen a antipatrón del modelo de dominio anémico . Los objetos anémicos generalmente generan muchos datos que deben pasarse, lo que aumenta el acoplamiento y hace que el resto del código sea engorroso. Además, la clase anémica oculta el hecho de que probablemente todavía esté pensando en el procedimiento en lugar de utilizar la orientación a objetos para encapsular los detalles.
IAP (for in purchases)
Ads (for showing ads)
¿Por qué estas clases deben ser singletons? Me parece que estas deberían ser clases de corta duración, que deberían mencionarse cuando sean necesarias y deconstruirse cuando el usuario finalice la compra o cuando ya no sea necesario mostrar los anuncios.
EntityComponentSystemManager (mananges entity creation and manipulation)
En otras palabras, ¿un constructor y una capa de servicio? ¿Por qué esta clase sin límites ni propósito claros, incluso allí en primer lugar?
PlayerProgress (passed levels, stars)
¿Por qué el progreso del jugador está separado de la clase de jugador? La clase Player debe saber cómo realizar un seguimiento de su propio progreso, si desea implementar el seguimiento del progreso en una clase diferente a la clase Player para la separación de responsabilidades, entonces PlayerProgress debe estar detrás del Player.
Box2dManager (manages the physics world)
Realmente no puedo comentar más sobre este sin saber lo que realmente hace esta clase, pero una cosa está clara es que esta clase está mal nombrada.
Analytics (for collecting some analytics)
SocialConnection (Facebook and Twitter login, share, friend list, ...)
Estas parecen ser las únicas clases en las que Singleton puede ser razonable, porque los objetos de conexión social no son realmente sus objetos. Las conexiones sociales son solo una sombra de objetos externos que viven en un servicio remoto. En otras palabras, este es un tipo de caché. Solo asegúrese de separar claramente la parte de almacenamiento en caché con la parte de servicio del servicio externo, porque la última parte no debería ser un singleton.
Una forma de evitar pasar instancias de estas clases es mediante el uso de pasar mensajes. En lugar de realizar una llamada directa a instancias de estas clases, en su lugar, envía un mensaje dirigido al servicio Analytic y SocialConnection de forma asíncrona, y estos servicios se suscriben para recibir estos mensajes y actuar en consecuencia. Dado que la cola de eventos ya es un singleton, al trampolizar la llamada, evita la necesidad de pasar una instancia real cuando se comunica con un singleton.