¿Cómo puedo evitar tener muchos singletons en la arquitectura de mi juego?

55

Yo uso el motor de juego cocos2d-x para crear juegos. El motor ya usa muchos singletons. Si alguien lo usó, entonces debería estar familiarizado con algunos de ellos:

Director
SimpleAudioEngine
SpriteFrameCache
TextureCache
EventDispatcher (was)
ArmatureDataManager
FileUtils
UserDefault

y muchos más con un total de aproximadamente 16 clases. Puede encontrar una lista similar en esta página: objetos Singleton en Cocos2d-html5 v3.0 Pero cuando quiero escribir en el juego necesito mucho más 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)
.....

¿Por qué creo que deberían ser solteros? Porque los necesitaré en lugares muy diferentes en mi juego, y el acceso compartido sería muy útil. En otras palabras, no sé qué crear en algún lugar y pasar punteros a toda mi arquitectura, ya que será muy difícil. Además, estas son cosas que solo necesito. En cualquier caso necesitaré varios, también puedo usar un patrón Multiton. Pero lo peor es que Singleton es el patrón más criticado debido a:

 - bad testability
 - no inheritance available
 - no lifetime control
 - no explicit dependency chain
 - global access (the same as global variables, actually)
 - ....

Puede encontrar algunas ideas aquí: https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons y https://stackoverflow.com/questions/4074154/when-should-the-singleton -patrón-no-ser-usado-además-de-lo-obvio

Entonces, creo que estoy haciendo algo mal. Creo que mi código huele mal . :) Me pregunto cómo los desarrolladores de juegos más experimentados resuelven este problema arquitectónico. Quiero verificar, tal vez todavía sea normal en el desarrollo del juego tener más de 30 singletons , considerados los que ya están en el motor del juego.

He pensado usar una Singleton-Facade que tendrá instancias de todas estas clases que necesito, pero cada una de ellas ya no sería singleton. Esto eliminará muchos problemas, y solo tendría un singleton que sería la Fachada misma. Pero en este caso tendré otro problema de diseño. La Fachada se convertirá en un OBJETO DE DIOS. Creo que esto también huele . Por lo tanto, no puedo encontrar una buena solución de diseño para esta situación. Por favor aconséjame.

Narek
fuente
26
La mayoría de las personas que argumentan en contra de Singleton no parecen darse cuenta de que el trabajo requerido para propagar algunos datos / funcionalidades a través de todo el código podría ser mucho más una fuente de errores que usar un Singleton. Básicamente, no están comparando, simplemente rechazan ==> sospecho que el anti-Singletonismo es una postura :-) Bueno, Singleton tiene fallas, así que si puedes evitarlo, hazlo para evitar los problemas que conlleva , ahora si no puedes, hazlo sin mirar atrás. Después de todo, Cocos2d parece funcionar bastante bien con sus 16 Singletons ...
GameAlchemist
66
Como recordatorio, esta es una pregunta sobre cómo realizar una tarea en particular. Esta no es una pregunta que solicite una discusión pro / con de singletons (que de todos modos estaría fuera de tema aquí). Además, recuerde que los comentarios deben usarse para solicitar una aclaración del autor de la pregunta, no como una plataforma para una discusión tangencial sobre los pros y los contras de los singletons. Si desea proporcionar su opinión, la forma correcta de hacerlo es proporcionar una respuesta legítima a la pregunta , en la que pueda moderar su solución con consejos a favor o en contra del patrón singleton.
Josh
¿Asumo que estos singletons son accesibles globalmente? Cuál sería una razón para evitar ese patrón.
Peter - Restablece a Monica el

Respuestas:

41

Inicializo mis servicios en mi clase de aplicación principal y luego los paso como punteros a lo que sea necesario para usarlos a través de los constructores o las funciones. Esto es útil por dos razones.

Uno, el orden de inicialización y limpieza es simple y claro. No hay forma de inicializar accidentalmente un servicio en otro lugar como puede hacerlo con un singleton.

Dos, está claro quién depende de qué. Puedo ver fácilmente qué clases necesitan qué servicios solo desde la declaración de clase.

Puede pensar que es molesto pasar todos estos objetos, pero si el sistema está bien diseñado, en realidad no está nada mal y aclara las cosas (para mí, de todos modos).

megadan
fuente
34
+1. Además, la "molestia" de tener que pasar referencias a sistemas alrededor ayuda a contrarrestar la dependencia de la fluencia; Si tiene que pasar algo a " todo " , es una buena señal de que tiene un problema de acoplamiento incorrecto en su diseño, y es mucho más explícito de esta manera que con el acceso global promiscuo a todo desde cualquier lugar.
Josh
2
En realidad, esto no proporciona una solución ... por lo que tiene su clase de programa con un montón de objetos singleton en él y ahora 10 niveles de código profundo que algo en la escena necesita uno de esos singletons ... ¿cómo se pasa?
Guerra del
Wardy: ¿Por qué tendrían que ser solteros? Claro, la mayoría de las veces puede estar pasando por una instancia en particular, pero lo que hace que un singleton sea un singleton es que NO PUEDE usar una instancia diferente. Con la inyección, puede pasar una instancia para un objeto y otra para el siguiente. El hecho de que no termines haciendo eso por cualquier razón, no significa que no puedas.
parar el
3
No son solteros. Son objetos regulares como cualquier otra cosa con un propietario bien definido que controla init / cleanup. Si tiene 10 niveles de profundidad sin acceso, tal vez tenga un problema de diseño. No todo necesita o debe tener acceso a renderizadores, audio y demás. De esta manera te hace pensar en tu diseño. Como una ventaja adicional para aquellos que prueban su código (¿qué?), La prueba unitaria también se vuelve mucho más fácil.
megadan
2
Sí, creo que la inyección de dependencia (con o sin un marco como Spring) es una solución perfecta para manejar esto. Encontré que este es un gran artículo para aquellos que se preguntan cómo estructurar mejor el código para evitar pasar cosas a todas partes: misko.hevery.com/2008/10/21/…
megadan
17

No discutiré sobre la maldad detrás de los singletons porque Internet puede hacerlo mejor que yo.

En mis juegos uso el patrón Localizador de servicios para evitar tener toneladas de Singletons / Managers.

El concepto es muy simple. Solo tiene un Singleton que actúa como la única interfaz para alcanzar lo que solía usar como Singleton. En lugar de tener varios Singleton, ahora solo tiene uno.

Llamadas como:

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

Se ve así, usando el patrón Localizador de servicios:

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

Como puede ver, cada Singleton está contenido en el Localizador de servicios.

Para tener una explicación más detallada, le sugiero que lea esta página sobre cómo usar el patrón de localización .

[ EDITAR ]: como se señala en los comentarios, el sitio gameprogrammingpatterns.com también contiene una sección muy interesante sobre Singleton .

Espero que ayude.

lvictorino
fuente
44
El sitio vinculado aquí, gameprogrammingpatterns.com, también tiene un capítulo sobre Singletons que parece relevante.
ajp15243
28
Los localizadores de servicios son solo elementos simples que mueven todos los problemas normales al tiempo de ejecución en lugar del tiempo de compilación. Sin embargo, lo que está sugiriendo es solo un registro estático gigante, que es casi mejor, pero sigue siendo esencialmente una masa de variables globales. Esto es simplemente una cuestión de mala arquitectura si muchos niveles necesitan acceso a los mismos objetos. La inyección de dependencia adecuada es lo que se necesita aquí, no con un nombre global diferente de los globales actuales.
Magus
2
¿Puedes dar más detalles sobre la "inyección de dependencia adecuada"?
Blue Wizard
17
Esto realmente no ayuda al problema. Esencialmente, hay muchos singletons fusionados en un singleton gigante. Ninguno de los problemas estructurales subyacentes se corrige o incluso se aborda, el código todavía apesta es más bonito ahora.
ClassicThunder
44
@classicthunder Si bien esto no aborda todos los problemas, esta es una gran mejora con respecto a Singletons porque aborda varios. En particular, el código que escribe ya no está acoplado a una sola clase, sino a una interfaz / categoría de clases. Ahora puede intercambiar fácilmente diferentes implementaciones de los diversos servicios.
jhocking
12

Leyendo todas estas respuestas, comentarios y artículos señalados, especialmente estos dos artículos brillantes,

Finalmente, llegué a la siguiente conclusión, que es una especie de respuesta a mi propia pregunta. El mejor enfoque es no ser perezoso y pasar dependencias directamente. Esto es explícito, comprobable, no hay acceso global, puede indicar un diseño incorrecto si tiene que pasar delegados muy profundos y en muchos lugares, etc. Pero en la programación, una talla no sirve para todos. Por lo tanto, todavía hay casos en los que no es útil pasarlos directamente. Por ejemplo, en caso de que creamos un marco de juego (una plantilla de código que se reutilizará como código base para desarrollar diferentes tipos de juegos) e incluiremos muchos servicios. Aquí no sabemos qué tan profundo puede pasar cada servicio y cuántos lugares será necesario. Depende de un juego en particular. Entonces, ¿qué hacemos en este tipo de casos? Utilizamos el patrón de diseño del Localizador de servicios en lugar de Singleton,

  1. No se programa para las implementaciones sino para las interfaces. Esto es muy esencial en términos de directores de OOP. En tiempo de ejecución, puede cambiar o incluso deshabilitar el servicio utilizando un patrón de objeto nulo. Esto no solo es importante en términos de flexibilidad, sino que ayuda directamente en las pruebas. Puede deshabilitar, simular, también puede usar el patrón Decorador, para agregar algunos registros y otras funciones también. Esta es una propiedad muy importante, que Singleton no tiene.
  2. Otra gran ventaja es que puede controlar la vida útil del objeto. En Singleton solo controlas el tiempo en que el objeto será generado por el patrón de Inicialización diferida. Pero una vez que se genera el objeto, vivirá hasta el final del programa. Pero en algunos casos le gustaría cambiar el servicio. O incluso apagarlo. Especialmente en los juegos, esta flexibilidad es muy importante.
  3. Combina / envuelve varios Singletons en una API compacta. Creo que también puede usar algunos Facade como Localizadores de servicios, que para una sola operación podrían usar varios servicios a la vez.
  4. Puede argumentar que todavía tenemos acceso global, que es el principal problema de diseño con Singleton. Estoy de acuerdo, pero necesitaremos este patrón solo y solo si queremos algún acceso global. No debemos quejarnos del acceso global, si creemos que la inyección directa de dependencia no es útil para nuestra situación, y necesitamos un acceso global. Pero incluso para este problema, hay una mejora disponible (consulte el segundo artículo anterior). Puede hacer que todos los servicios sean privados, y puede heredar de la clase Service Locator todas las clases que cree que deberían usar los servicios. Esto puede reducir significativamente el acceso. Y considere que en el caso de Singleton no puede usar la herencia debido al constructor privado.

También debemos considerar que este patrón se usó en Unity, LibGDX, XNA. Esto no es una ventaja, pero es una prueba de la usabilidad del patrón. Tenga en cuenta que estos motores fueron desarrollados por muchos desarrolladores inteligentes durante mucho tiempo y durante las fases de evolución del motor todavía no encontraron una solución mejor.

En conclusión, creo que Service Locator puede ser muy útil para los escenarios descritos en mi pregunta, pero aún así debería evitarse si es posible. Como regla general, úselo si es necesario.

EDITAR: Después de un año de trabajo y utilizando DI directo y teniendo problemas con grandes constructores y muchas delegaciones, he investigado más y he encontrado un buen artículo que habla sobre los pros y los contras sobre los métodos DI y Localizador de servicios. Citando este artículo ( http://www.martinfowler.com/articles/injection.html ) de Martin Fowler que explica cuándo realmente los Localizadores de servicios son malos:

Entonces, el problema principal es para las personas que escriben código que espera ser utilizado en aplicaciones fuera del control del escritor. En estos casos, incluso una suposición mínima sobre un Localizador de servicios es un problema.

Pero de todos modos, si nos quiere DI por primera vez, debería leer este http://misko.hevery.com/2008/10/21/dependency-injection-myth-reference-passing/ artículo (señalado por @megadan) , prestando mucha atención a la Ley de Demeter (LoD) o al principio de menor conocimiento .

Narek
fuente
Mi intolerancia personal hacia los localizadores de servicios surge principalmente de mis intentos de probar el código que los usa. En interés de las pruebas de caja negra, llama al constructor para hacer una instancia para probar. Se puede lanzar una excepción de inmediato, o en cualquier llamada a un método, y puede que no haya propiedades públicas que lo lleven a las dependencias que faltan. Esto puede ser menor si tiene acceso a la fuente, pero aún así viola el Principio de Menos Sorpresa (un problema frecuente con Singletons). Usted ha sugerido pasar el localizador de servicios, que alivia parte de esto, y se lo debe recomendar.
Magus
3

No es raro que partes de una base de código se consideren objetos de piedra angular o una clase básica, pero eso no justifica que su ciclo de vida se dicte como Singleton.

Los programadores a menudo confían en el patrón Singleton como un medio de conveniencia y holgazanería pura en lugar de adoptar el enfoque alternativo y ser un poco más detallado e imponer relaciones de objeto entre sí en una mansión explícita.

Entonces pregúntese cuál es más fácil de mantener.

Si eres como yo, prefiero poder abrir un archivo de encabezado y leer brevemente los argumentos del constructor y los métodos públicos para ver qué dependencias requiere un objeto en lugar de tener que inspeccionar cientos de líneas de código en un archivo de implementación.

Si ha convertido un objeto en un Singleton y luego se da cuenta de que necesita ser capaz de soportar múltiples instancias de dicho objeto, imagine los cambios radicales necesarios si esta clase fue algo que usó bastante en toda su base de código. La mejor alternativa es hacer explícitas las dependencias, incluso si el objeto solo se asigna una vez y si surge la necesidad de admitir múltiples instancias, puede hacerlo de manera segura sin tales preocupaciones.

Como otros han señalado, el uso del patrón Singleton también ofusca el acoplamiento que a menudo significa un diseño deficiente y una alta cohesión entre los módulos. Tal cohesión generalmente no es deseable y conduce a un código frágil que puede ser difícil de mantener.

Naros
fuente
-1: Esta respuesta describe erróneamente el patrón singleton, y todos sus argumentos describen los problemas con la implementación deficiente del patrón. El singleton adecuado es una interfaz y evita todos los problemas que describió (incluido el cambio a la no soltería, que es un escenario que GoF aborda explícitamente). Si es difícil refactorizar el uso de un singleton, refactorizar el uso de una referencia de objeto explícita solo puede ser más difícil, no menos. Si singleton de alguna manera causa MÁS acoplamiento de código, simplemente se hizo mal.
Seth Battin
1

Singleton es un patrón famoso, pero es bueno saber los propósitos a los que sirve, y sus ventajas y desventajas.

Realmente tiene sentido si no hay ninguna relación .
Si puede manejar su componente con un objeto totalmente diferente (sin dependencia fuerte) y espera tener el mismo comportamiento, el singleton puede ser una buena opción.

Por otro lado, si necesita una pequeña funcionalidad , que solo se usa en ciertos momentos, debería preferir pasar referencias .

Como mencionó, los singletons no tienen control de por vida. Pero la ventaja es que tienen una inicialización perezosa.
Si no llama a ese singleton en la vida de su aplicación, no se instanciará. Pero dudo que construyas singletons y no los uses.

Debe ver un singleton como un servicio en lugar de un componente .

Singletons funciona, nunca rompieron ninguna aplicación. Pero sus carencias (las cuatro que has dado) son razones por las que no harás una.

Crazyrems
fuente
1

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.

Lie Ryan
fuente
-1

Los solteros para mí SIEMPRE son malos.

En mi arquitectura, creé una colección de GameServices que administra la clase de juego. Puedo buscar agregar y quitar de esta colección según sea necesario.

Al decir que algo tiene un alcance global, básicamente estás diciendo "esta acción es Dios y no tiene maestría". En los ejemplos que proporcionas arriba, diría que la mayoría de ellos son clases auxiliares o GameServices, en cuyo caso el juego sería responsable de ellos.

Pero ese es solo mi diseño en mi motor, su situación puede ser diferente.

Poniéndolo más directamente ... El único singleton real es el juego, ya que posee cada parte del código.

Esta respuesta se basa en la naturaleza subjetiva de la pregunta y se basa únicamente en mi opinión, puede que no sea correcta para todos los desarrolladores.

Editar: según lo solicitado ...

Ok, esto es de mi cabeza ya que no tengo mi código frente a mí en este momento, pero esencialmente en la raíz de cualquier juego está la clase Game, que es verdaderamente un singleton ... ¡tiene que serlo!

Entonces agregué lo siguiente ...

class Game
{
   IList<GameService> Services;

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

Ahora también tengo una clase de sistema (estática) que contiene todos mis singletons de todo el programa (contiene muy pocas opciones de juego y configuración y eso es todo) ...

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; }
}

Y el uso ...

Desde cualquier lugar puedo hacer algo como

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

Este enfoque significa que mi juego "posee" cosas que normalmente se considerarían un "singleton" y puedo extender la clase base de GameService como quiera. Tengo interfaces como IUpdateable que mi juego busca y llama a cualquier servicio que lo implemente según sea necesario para situaciones específicas.

También tengo servicios que heredan / extienden otros servicios para escenarios de escenas más específicos.

Por ejemplo ...

Tengo un servicio de física que maneja mis simulaciones físicas, pero dependiendo de la escena, la simulación física puede necesitar un comportamiento ligeramente diferente.

Guerra
fuente
1
¿No deberían instanciarse los servicios del juego solo una vez? Si es así, es solo un patrón singleton que no se aplica a nivel de clase, que es peor que un singleton implementado correctamente.
GameAlchemist
2
@Wardy No entiendo claramente lo que has hecho, pero parece la Singleton-Facade que describí y debería convertirse en un objeto de DIOS, ¿verdad?
Narek
10
Es solo un patrón Singleton implementado a nivel de juego. Básicamente, su aspecto más interesante es permitirte fingir que no estás usando Singleton. Útil si tu jefe es de la iglesia antipatrón.
GameAlchemist
2
No estoy seguro de cómo esto es efectivamente diferente del uso de Singletons. ¿No es la única diferencia en la forma específica en que accede a su único servicio existente de este tipo? Es decir, en var tService = Sys.Game.getService<T>(); tService.Something();lugar de omitir la getServiceparte y acceder a ella directamente.
Christian
1
@Christian: De hecho, eso es exactamente el antipatrón del Localizador de servicios. Aparentemente, esta comunidad todavía piensa que es un patrón de diseño recomendado.
Magus
-1

Un ingrediente esencial de la eliminación de singleton que los oponentes de singleton a menudo no consideran es agregar lógica adicional para lidiar con el estado mucho más complicado que los objetos encapsulan cuando los singletons dan paso a los valores de instancia. De hecho, incluso definir el estado de un objeto después de reemplazar un singleton con una variable de instancia puede ser difícil, pero los programadores que reemplazan singletons con variables de instancia deben estar preparados para lidiar con él.

Compare, por ejemplo, Java HashMapcon .NET Dictionary. Ambos son bastante similares, pero tienen una diferencia clave: Java HashMapestá codificado para usar equalsy hashCode(efectivamente usa un comparador singleton) mientras que .NET Dictionarypuede aceptar una instancia de IEqualityComparer<T>como parámetro de constructor. El costo en tiempo de ejecución de mantener el IEqualityCompareres mínimo, pero la complejidad que introduce no lo es.

Debido a que cada Dictionaryinstancia encapsula un IEqualityComparer, su estado no es solo un mapeo entre las claves que realmente contiene y sus valores asociados, sino un mapeo entre los conjuntos de equivalencia definidos por las claves y el comparador y sus valores asociados. Si dos instancias d1y d2de Dictionarymantener referencias a la misma IEqualityComparer, será posible producir una nueva instancia d3tal que para cualquiera x, d3.ContainsKey(x)será igual d1.ContainsKey(x) || d2.ContainsKey(x). Si, sin embargo, d1y d2puede contener referencias a diferentes comparadores de igualdad arbitrarias, habrá, en general, no hay manera de combinarlos con el fin de garantizar que condición se cumple.

Agregar variables de instancia en un esfuerzo por eliminar los singletons, pero no tener en cuenta adecuadamente los posibles nuevos estados que podrían implicar esas variables de instancia puede crear un código que termina siendo más frágil de lo que hubiera sido con un singleton. Si cada instancia de objeto tiene un campo separado, pero las cosas funcionarán mal a menos que todas identifiquen la misma instancia de objeto, entonces todos los campos nuevos que han comprado son un mayor número de formas en que las cosas pueden salir mal.

Super gato
fuente
1
Si bien este es definitivamente un aspecto interesante para la discusión, no creo que realmente aborde la pregunta que realmente hace el OP.
Josh
1
@JoshPetrie: Los comentarios en la publicación original (por ejemplo, de Magus) sugirieron que el costo de tiempo de ejecución de llevar referencias para evitar el uso de singletons no fue grande. Como tal, consideraría relevantes los costos semánticos de tener cada objeto encapsulando referencias con el propósito de evitar los singletons. Se deben evitar los singletons en los casos en que se pueden evitar de manera barata y fácil, pero el código que no requiere abiertamente un singleton pero que puede romperse tan pronto como existan múltiples instancias de algo es peor que el código que usa un singleton.
supercat
2
Las respuestas no son para abordar los comentarios, son para responder directamente a la pregunta.
ClassicThunder
@ClassicThunder: ¿Te gusta más la edición?
Supercat