secuencia patron explicacion example ejemplos diseño diagrama desventajas consecuencias code c++ design-patterns singleton

c++ - patron - Problemas con el patrón Singleton



patron singleton php (7)

He estado leyendo sobre el patrón de Singleton en los últimos días. La percepción general es que los escenarios donde se requiere son muy pocos (si no es raro) probablemente porque tiene su propio conjunto de problemas, como

  • En un entorno de recolección de basura puede ser un problema con respecto a la administración de la memoria.
  • En un entorno multiproceso, puede causar cuellos de botella e introducir problemas de sincronización.
  • Dolor de cabeza de las pruebas prespectivas.

Estoy empezando a tener las ideas detrás de estos problemas, pero no estoy totalmente seguro de estas preocupaciones. Al igual que en el caso del problema de la recolección de basura, el uso de la estática en la implementación de singleton (que es inherente al patrón), ¿es esa la preocupación? Ya que significa que la instancia estática durará hasta la aplicación. ¿Es algo que degrada la gestión de la memoria (solo significa que la memoria asignada al patrón singleton no se liberará)?

Por supuesto, en una configuración multiproceso, tener todos los hilos en disputa por la instancia singleton sería un cuello de botella. Pero cómo el uso de este patrón causa problemas de sincronización (seguramente podemos usar un mutex o algo así para sincronizar el acceso).

Desde una perspectiva de prueba (¿de la unidad?), Dado que los singleton usan métodos estáticos (que son difíciles de burlar o tropezar) pueden causar problemas. No estoy seguro de esto ¿Puede alguien dar más detalles sobre esta preocupación de prueba?

Gracias.


En un entorno de recolección de basura puede ser un problema con respecto a la administración de memoria

En las implementaciones de singleton típicas, una vez que crea el singleton, nunca puede destruirlo. Esta naturaleza no destructiva a veces es aceptable cuando el singleton es pequeño. Sin embargo, si el singleton es masivo, entonces innecesariamente está usando más memoria de la que debería.

Este es un problema mayor en los idiomas en los que tiene un recolector de basura (como Java, Python, etc.) porque el recolector de basura siempre creerá que el singleton es necesario. En C ++, puede hacer trampa delete el puntero. Sin embargo, esto abre su propia lata de gusanos porque se supone que es un singleton, pero al eliminarlo, está permitiendo la creación de un segundo.

En la mayoría de los casos, este uso excesivo de memoria no degrada el rendimiento de la memoria, pero se puede considerar lo mismo que una pérdida de memoria. Con un singleton grande, está desperdiciando memoria en la computadora o el dispositivo de su usuario. (Puede ejecutar la fragmentación de memoria si asigna un singleton enorme, pero esto generalmente no es una preocupación).

En un entorno multiproceso, puede causar cuellos de botella e introducir problemas de sincronización.

Si cada hilo está accediendo al mismo objeto y está utilizando un mutex, cada hilo debe esperar hasta que otro haya desbloqueado el singleton. Y si los hilos dependen en gran medida del singleton, degradarás el rendimiento a un entorno de hilo único porque un hilo pasa la mayor parte de su vida esperando.

Sin embargo, si su dominio de aplicación lo permite, puede crear un objeto para cada hilo, de esta manera el hilo no pierde tiempo esperando y en su lugar hace el trabajo.

Dolor de cabeza de las pruebas prespectivas.

Notablemente, el constructor de un singleton solo puede ser probado una vez. Tienes que crear un conjunto de pruebas completamente nuevo para probar el constructor de nuevo. Esto está bien si tu constructor no toma ningún parámetro, pero una vez que aceptas un parámetro, ya no puedes utilizar el control efectivo de la unidad.

Además, no puede anular el singleton con la misma eficacia y el uso de objetos falsos se vuelve difícil de usar (hay formas de evitar esto, pero es más problemático de lo que vale). Sigue leyendo para obtener más información sobre esto ...

(¡Y también conduce a un mal diseño!)

Los singletons son también un signo de un diseño pobre. Algunos programadores quieren hacer que su clase de base de datos sea singleton. "Nuestra aplicación nunca usará dos bases de datos", generalmente piensan. Pero llegará un momento en que puede tener sentido utilizar dos bases de datos, o pruebas de unidades, que deseará utilizar dos bases de datos SQLite diferentes. Si usó un singleton, tendrá que hacer algunos cambios serios a su aplicación. Pero si usó objetos regulares desde el principio, puede aprovechar el OOP para realizar su tarea de manera eficiente y puntual.

La mayoría de los casos de singleton son el resultado de que el programador es flojo. No desean pasar un objeto (por ejemplo, objeto de base de datos) a un conjunto de métodos, por lo que crean un singleton que cada método utiliza como un parámetro implícito. Pero este enfoque muerde por las razones anteriores.

Intenta nunca usar un singleton, si puedes. A pesar de que puede parecer un buen enfoque desde el principio, generalmente siempre conduce a un diseño deficiente y difícil de mantener el código en la línea.


Acerca de esta preocupación de prueba de la unidad. Los principales problemas parecen no ser probar los singletons en sí mismos, sino probar los objetos que los utilizan .

Dichos objetos no se pueden aislar para la prueba, ya que tienen dependencias en singletons que están ocultos y son difíciles de eliminar. Se pone aún peor si el singleton representa una interfaz para un sistema externo (conexión DB, procesador de pagos, unidad de disparo ICBM). Probar un objeto así podría escribir inesperadamente en DB, enviar algo de dinero que sepa dónde o incluso disparar algunos misiles intercontinentales.


Al evaluar el patrón Singleton, debe preguntar "¿Cuál es la alternativa? ¿Ocurrirían los mismos problemas si no usara el patrón Singleton?"

La mayoría de los sistemas tienen alguna necesidad de Big Global Objects . Estos son artículos que son grandes y caros (por ejemplo, administradores de conexión de base de datos) o contienen información de estado generalizada (por ejemplo, información de bloqueo).

La alternativa a Singleton es tener este Big Global Object creado al inicio, y pasarlo como un parámetro a todas las clases o métodos que necesitan acceso a este objeto.

¿Ocurrirían los mismos problemas en el caso no único? Examinemos uno por uno:

  • Administración de memoria : el objeto global grande existiría cuando se inició la aplicación y el objeto existirá hasta que se apague. Como solo hay un objeto, ocupará exactamente la misma cantidad de memoria que el caso singleton. El uso de la memoria no es un problema. (@MadKeithV: orden de destrucción en el cierre es un problema diferente).

  • Multithreading y cuellos de botella : todos los subprocesos necesitarían acceder al mismo objeto, independientemente de si pasaron este objeto como parámetro o si llamaron a MyBigGlobalObject.GetInstance () . So Singleton o no, aún tendría los mismos problemas de sincronización (que afortunadamente tienen soluciones estándar). Esto tampoco es un problema.

  • Pruebas unitarias : si no está utilizando el patrón Singleton, puede crear el objeto global grande al comienzo de cada prueba, y el recolector de basura se lo quitará cuando finalice la prueba. Cada prueba comenzará con un nuevo entorno limpio que no haya sido afectado por la prueba anterior. Alternativamente, en el caso de Singleton, el único objeto vive TODAS las pruebas y puede contaminarse fácilmente. Entonces sí, el patrón Singleton realmente muerde cuando se trata de pruebas unitarias.

Mi preferencia: debido al problema de la unidad de prueba solo, tiendo a evitar el patrón de Singleton. Si es uno de los pocos entornos donde no tengo pruebas de unidad (por ejemplo, la capa de interfaz de usuario), entonces podría usar Singleton, de lo contrario, los evitaré.


Estoy de acuerdo con el sentimiento anterior de que con frecuencia se utilizan para que no tenga que pasar una discusión por todos lados. Lo hago. El ejemplo típico es su objeto de registro del sistema. Normalmente lo convertiría en un singleton, así no tengo que pasarlo por todo el sistema.

Encuesta - En el ejemplo del objeto de registro, ¿cuántos de ustedes (mano alzada) agregarían un argumento adicional a cualquier rutina que pudiera necesitar registrar algo -vs- usar un singleton?


Mi argumento principal contra singletons es, básicamente, que combinan dos propiedades malas.

Las cosas que mencionas pueden ser un problema, claro, pero no tienen que serlo. La sincronización puede solucionarse, solo se convierte en un cuello de botella si muchos subprocesos acceden con frecuencia al singleton, y así sucesivamente. Esos problemas son molestos, pero no desbaratan el trato.

El problema mucho más fundamental con singletons es que lo que están tratando de hacer es fundamentalmente malo.

Un singleton, según lo define el GoF, tiene dos propiedades:

  • Es accesible a nivel mundial, y
  • Impide que la clase se ejemplifique alguna vez más de una vez.

El primero debe ser simple. Los Globales son, en general, malos. Si no quieres un global, entonces tampoco quieres un singleton.

El segundo problema es menos obvio, pero fundamentalmente intenta resolver un problema inexistente.

¿Cuándo fue la última vez que accidentalmente creó una instancia de una clase, y en su lugar intentó reutilizar una instancia existente?

¿Cuándo fue la última vez que accidentalmente std::ostream() << "hello world << std::endl " std::ostream() << "hello world << std::endl ", cuando quiso decir " std::cout << "hello world << std::endl "?

Simplemente no sucede. Por lo tanto, no necesitamos evitar esto en primer lugar.

Pero, lo que es más importante, la sensación instintiva de que "solo debe existir una instancia" casi siempre es incorrecta. Lo que generalmente queremos decir es "actualmente solo puedo ver un uso para una instancia".

pero "Solo puedo ver un uso para una instancia" no es lo mismo que "la aplicación se vendrá abajo si alguien se atreve a crear dos instancias".

En este último caso, un singleton podría estar justificado. pero en el primero, es realmente una elección de diseño prematura.

Por lo general, terminamos queriendo más de una instancia.

A menudo termina necesitando más de un registrador. Está el registro al que le escribe mensajes limpios y estructurados, para que el cliente lo supervise, y hay uno al que puede descargar datos de depuración para su propio uso.

También es fácil imaginar que podrías terminar usando más de una base de datos.

O la configuración del programa. Claro, solo un conjunto de configuraciones puede estar activo a la vez. Pero mientras están activos, el usuario puede ingresar al diálogo de "opciones" y configurar un segundo conjunto de configuraciones. Aún no los ha aplicado, pero una vez que golpea "bien", deben intercambiarse y reemplazar el conjunto actualmente activo. Y eso significa que hasta que llegue a "ok", existen dos conjuntos de opciones.

Y más generalmente, pruebas unitarias:

Una de las reglas fundamentales de las pruebas unitarias es que deben ejecutarse de forma aislada. Cada prueba debe configurar el entorno desde cero, ejecutar la prueba y destruir todo. Lo que significa que cada prueba querrá crear un nuevo objeto singleton, ejecutar la prueba en su contra y cerrarlo.

Lo que obviamente no es posible, porque un singleton se crea una vez, y solo una vez. No puede ser eliminado. No se pueden crear nuevas instancias.

Así que, en última instancia, el problema con los singletons no es tecnicismos como "es difícil hacer que la seguridad de los hilos sea correcta", sino mucho más fundamental: "en realidad, no aportan nada positivo a tu código. Agregan dos características, cada una de ellas negativa" . a tu base de código. ¿Quién querría eso? "


No necesariamente equipararía Singletons con Globals. Nada debería impedir que un desarrollador pase una instancia del objeto, singleton o de otro modo, como un parámetro, en lugar de invocarlo desde el aire. La intención de ocultar su accesibilidad global podría incluso hacerse ocultando su función getInstance a algunos amigos selectos.

En cuanto a la falla de la unidad de prueba, la unidad significa pequeña, por lo que volver a invocar la aplicación para probar el singleton de una manera diferente parece razonable, a menos que me falta algo el punto.


Si no has visto el artículo Singletons son mentirosos patológicos , deberías leerlo también. Discute cómo las interconexiones entre singletons están ocultas de la interfaz, por lo que la forma en que necesita construir el software también está oculta en la interfaz.

Hay enlaces a un par de otros artículos sobre singletons del mismo autor.