He leído diferentes opiniones sobre el patrón singleton. Algunos sostienen que debe evitarse a toda costa y otros dicen que puede ser útil en ciertas situaciones.
Una situación en la que uso singletons es cuando necesito una fábrica (digamos un objeto f de tipo F) para crear objetos de cierta clase A. La fábrica se crea una vez usando algunos parámetros de configuración y luego se usa cada vez que un objeto de El tipo A es instanciado. Entonces, cada parte del código que quiere instanciar A obtiene el singleton f y crea la nueva instancia, por ejemplo
F& f = F::instance();
boost::shared_ptr<A> a = f.createA();
Entonces, el escenario general es que
- Solo necesito una instancia de una clase, ya sea por razones de optimización (no necesito varios objetos de fábrica) o para compartir un estado común (por ejemplo, la fábrica sabe cuántas instancias de A todavía puede crear)
- Necesito una forma de tener acceso a esta instancia f de F en diferentes lugares del código.
No estoy interesado en la discusión sobre si este patrón es bueno o malo, pero suponiendo que quiero evitar usar un singleton, ¿qué otro patrón puedo usar?
Las ideas que tuve fueron (1) obtener el objeto de fábrica de un registro o (2) crear la fábrica en algún momento durante el inicio del programa y luego pasar la fábrica como parámetro.
En la solución (1), el registro en sí es un singleton, por lo que acabo de cambiar el problema de no usar un singleton de fábrica al registro.
En el caso (2) necesito alguna fuente inicial (objeto) de donde proviene el objeto de fábrica, así que me temo que volvería a recurrir a otro singleton (el objeto que proporciona mi instancia de fábrica). Siguiendo esta cadena de singletons, tal vez pueda reducir el problema a un singleton (la aplicación completa) mediante el cual todos los demás singletons se manejan directa o indirectamente.
¿Sería esta última opción (usando un singleton inicial que crea todos los demás objetos únicos e inyecta todos los demás singletons en los lugares correctos) una solución aceptable? ¿Es esta la solución que se sugiere implícitamente cuando se aconseja no usar singletons, o cuáles son otras soluciones, por ejemplo, en el ejemplo ilustrado anteriormente?
EDITAR
Como creo que algunos han entendido mal el punto de mi pregunta, aquí hay más información. Como se explica, por ejemplo , aquí , la palabra singleton puede indicar (a) una clase con un solo objeto de instancia y (b) un patrón de diseño utilizado para crear y acceder a dicho objeto.
Para aclarar las cosas, usemos el término objeto único para (a) y patrón singleton para (b). Entonces, sé cuáles son el patrón singleton y la inyección de dependencia (por cierto, últimamente he estado usando DI para eliminar instancias del patrón singleton de algún código en el que estoy trabajando).
Mi punto es que, a menos que el gráfico de todo el objeto sea instanciado de un solo objeto que vive en la pila del método principal, siempre será necesario acceder a algunos objetos únicos a través del patrón singleton.
Mi pregunta es si tener la creación y el cableado completos del gráfico de objetos depende del método principal (por ejemplo, a través de un poderoso marco DI que no usa el patrón en sí) es la única solución libre de patrón único .
Respuestas:
Su segunda opción es un buen camino a seguir: es un tipo de inyección de dependencia, que es el patrón utilizado para compartir el estado en su programa cuando desea evitar los valores únicos y las variables globales.
No puede evitar el hecho de que algo tiene que crear su fábrica. Si ese algo es la aplicación, que así sea. El punto importante es que a su fábrica no le debe importar qué objeto lo creó, y los objetos que reciben la fábrica no deberían depender de dónde vino la fábrica. No permita que sus objetos apunten a la aplicación singleton y le pidan la fábrica; haga que su aplicación cree la fábrica y se la dé a los objetos que la necesiten.
fuente
El propósito de un singleton es hacer cumplir que solo una instancia puede existir dentro de un cierto reino. Esto significa que un singleton es útil si tiene fuertes razones para imponer el comportamiento de singleton; En la práctica, esto rara vez es el caso, y el procesamiento múltiple y el subprocesamiento múltiple ciertamente desdibujan el significado de 'único': ¿es una instancia por máquina, por proceso, por subproceso, por solicitud? ¿Y su implementación de singleton se ocupa de las condiciones de carrera?
En lugar de singleton, prefiero usar cualquiera de:
La razón es que un singleton es un estado global disfrazado, lo que significa que introduce un alto grado de acoplamiento en toda la aplicación: cualquier parte de su código puede tomar la instancia de singleton desde cualquier lugar con un mínimo esfuerzo. Si usa objetos locales o pasa instancias, tiene mucho más control y puede mantener sus ámbitos pequeños y sus dependencias estrechas.
fuente
La mayoría de las personas (incluido usted) no entienden completamente cuál es el patrón Singleton en realidad. El patrón Singleton solo significa que existe una instancia de una clase y hay algún mecanismo para que el código de toda la aplicación obtenga una referencia a esa instancia.
En el libro GoF, el método de fábrica estático que devuelve una referencia a un campo estático fue solo un ejemplo de cómo podría ser ese mecanismo, y uno con graves inconvenientes. Desafortunadamente, todos y su perro se aferraron a ese mecanismo y pensaron que de eso se trata Singleton.
Las alternativas que cita son, de hecho, también Singletons, solo con un mecanismo diferente para obtener la referencia. (2) claramente da como resultado demasiada transferencia, a menos que necesite la referencia en solo unos pocos lugares cerca de la raíz de la pila de llamadas. (1) suena como un marco de inyección de dependencia cruda, entonces, ¿por qué no usar uno?
La ventaja es que los marcos DI existentes son flexibles, potentes y bien probados. Pueden hacer mucho más que solo administrar Singletons. Sin embargo, para utilizar plenamente sus capacidades, funcionan mejor si estructura su aplicación de una manera determinada, lo que no siempre es posible: idealmente, hay objetos centrales que se adquieren a través del marco DI y tienen todas sus dependencias pobladas de forma transitiva y están luego ejecutado.
Editar: en última instancia, todo depende del método principal de todos modos. Pero tiene razón: la única forma de evitar el uso de global / static por completo es tener todo configurado desde el método principal. Tenga en cuenta que DI es más popular en entornos de servidor donde el método principal es opaco y configura el servidor y el código de la aplicación consiste básicamente en devoluciones de llamada que se instancian y llaman por el código del servidor. Pero la mayoría de los marcos DI también permiten el acceso directo a su registro central, por ejemplo, el ApplicationContext de Spring, para "casos especiales".
Entonces, básicamente, lo mejor que la gente ha descubierto hasta ahora es una combinación inteligente de las dos alternativas que mencionó.
fuente
Hay algunas alternativas:
Inyección de dependencia
Cada objeto tiene sus dependencias pasadas cuando se creó. Normalmente, un marco o un pequeño número de clases de fábrica son responsables de crear y conectar los objetos
Registro de servicio
Cada objeto pasa un objeto de registro de servicio. Proporciona métodos para solicitar varios objetos diferentes que proporcionan diferentes servicios. El marco de Android usa este patrón
Administrador de raíz
Hay un solo objeto que es la raíz del gráfico de objetos. Siguiendo los enlaces de este objeto, se puede encontrar cualquier otro objeto. El código tiende a verse así:
fuente
Singleton::instance
en todas partes en el código del cliente.Si sus necesidades del singleton se pueden reducir a una sola función, ¿por qué no usar una simple función de fábrica? Las funciones globales (probablemente un método estático de clase F en su ejemplo) son inherentemente singletons, con unicidad forzada por el compilador y el enlazador.
Es cierto que esto se rompe cuando la operación del singleton no se puede reducir a una sola llamada de función, aunque quizás un pequeño conjunto de funciones relacionadas pueda ayudarlo.
Dicho todo esto, los puntos 1 y 2 de tu pregunta dejan en claro que realmente solo quieres uno de algo. Dependiendo de su definición del patrón singleton, ya lo está utilizando o muy cerca. No creo que pueda tener uno de algo sin que sea un singleton o al menos muy cerca de uno. Está demasiado cerca del significado de singleton.
Como sugiere, en algún momento debe tener uno de algo, por lo que tal vez el problema no sea tener una sola instancia de algo, sino tomar medidas para prevenir (o al menos desalentar o minimizar) el abuso del mismo. Mover tanto estado fuera del "singleton" a los parámetros como sea posible es un buen comienzo. Limitar la interfaz al singleton también ayuda, ya que hay menos oportunidades de abuso de esa manera. A veces solo tienes que absorberlo y hacer que el singleton sea muy robusto, como el montón o el sistema de archivos.
fuente
Si está utilizando un lenguaje multiparadigm como C ++ o Python, una alternativa a una clase singleton es un conjunto de funciones / variables envueltas en un espacio de nombres.
Hablando conceptualmente, un archivo C ++ con variables globales gratuitas, funciones globales gratuitas y variables estáticas utilizadas para ocultar información, todo envuelto en un espacio de nombres, le da casi el mismo efecto que una "clase" singleton.
Solo se descompone si quieres herencia. He visto muchos singletons que hubieran sido mejores de esta manera.
fuente
Encapsulación Singletons
De los casos. Su aplicación está orientada a objetos y requiere 3 o 4 singletons específicos, incluso si tiene más clases.
Antes del ejemplo (C ++ como pseudocódigo):
Una forma es eliminar parcialmente algunos singletons (globales), encapsulándolos en un singleton único, que tiene como miembros, los otros singletons.
De esta manera, en lugar de "varios singletons, enfoque, versus, ningún singleton, enfoque", tenemos el "encapsular todos los singletons en uno, enfoque".
Después del ejemplo (C ++ como pseudocódigo):
Tenga en cuenta que los ejemplos son más como pseudocódigo e ignoran errores menores o errores de sintaxis, y considere la solución a la pregunta.
También hay otra cosa a tener en cuenta, porque el lenguaje de programación utilizado puede afectar cómo implementar su implementación singleton o nontonton.
Aclamaciones.
fuente
Sus requisitos originales:
No se alinee con la definición de singleton (y a lo que más bien se referirá más adelante). De GoF (mi versión es 1995) página 127:
Si solo necesita una instancia, eso no le impide hacer más.
Si desea una única instancia accesible a nivel mundial, cree una única instancia accesible a nivel mundial . No es necesario tener un patrón nombrado para todo lo que haces. Y sí, las instancias individuales accesibles globalmente suelen ser malas. Darles un nombre no los hace menos malos .
Para evitar la accesibilidad global, los enfoques comunes de "hacer una instancia y pasarla" o "hacer que el propietario de dos objetos los pegue" funcionan bien.
fuente
¿Qué tal usar el contenedor IoC? Con un poco de pensamiento, puede terminar con algo en la línea de:
Tendría que especificar qué implementación
IFoo
usar en un inicio, pero esta es una configuración única que puede reemplazar fácilmente más adelante. La vida útil de una instancia resuelta puede controlarse normalmente mediante un contenedor IoC.El método vacío singleton estático podría reemplazarse con:
El método getter de singleton estático podría reemplazarse con:
El ejemplo es relevante para .NET, pero estoy seguro de que se puede lograr muy similar en otros idiomas.
fuente