¿Existen alternativas viables al patrón GOF Singleton?

91

Seamos sinceros. El patrón Singleton es un tema muy controvertido con los programadores de hordas en ambos lados de la cerca. Hay quienes sienten que el Singleton no es más que una variable global glorificada, y otros que juran por un patrón y lo usan incesantemente. Sin embargo, no quiero que la controversia Singleton esté en el centro de mi pregunta. Todo el mundo puede tener un tira y afloja y luchar y ver quién gana por lo que a mí respecta . Lo que estoy tratando de decir es que no creo que haya una sola respuesta correcta y no intento intencionalmente inflamar las disputas partidistas. Simplemente estoy interesado en alternativas de singleton cuando hago la pregunta:

¿Hay alguna alternativa específica al patrón GOF Singleton?

Por ejemplo, muchas veces cuando he usado el patrón singleton en el pasado, simplemente estoy interesado en preservar el estado / valores de una o varias variables. El estado / valores de las variables, sin embargo, se pueden conservar entre cada instanciación de la clase usando variables estáticas en lugar de usar el patrón singleton.

¿Qué otras ideas tienes?

EDITAR: Realmente no quiero que esta sea otra publicación sobre "cómo usar el singleton correctamente". Nuevamente, estoy buscando formas de evitarlo. Por diversión, ¿vale? Supongo que estoy haciendo una pregunta puramente académica con tu mejor voz de avance de película: "En un universo paralelo donde no hay singleton, ¿qué podríamos hacer?"

Codificación sin comentarios
fuente
¿Qué? No es ni bueno ni malo, pero ¿cómo puedo reemplazarlo? Para todas las personas que dicen que es bueno, no participen. Todos los que dicen que es malo, demuéstrenlo mostrándome cómo puedo vivir sin él. Suena discutible para mí.
S.Lott
@CodingWithoutComents: leyó la publicación completa. Así es como tuve la sensación de "no respondas si crees que los Singleton están bien".
S.Lott
2
Bueno, si así fue, me disculpo. Pensé que había dado pasos importantes para evitar la polarización. Pensé que hice la pregunta de una manera que tanto los amantes como los que odian a Singletons podrían llegar a la conclusión de que, como programadores, todos tenemos opciones, que nunca es solo una forma correcta
CodingWithoutComments
Si utilizo Singletons, no tengo ninguna contribución posible sobre cómo solucionarlos. Suena polarizante para mí.
S.Lott
9
Uso Singletons todos los días, pero ¿eso me impide pensar que podría haber una mejor manera de hacer las cosas? Los patrones de diseño solo existen desde hace 14 años. ¿Los tomo como verdad bíblica? ¿Dejamos de intentar pensar fuera de la caja? ¿No intentamos avanzar en la disciplina de la informática?
CodingWithoutComments

Respuestas:

76

Alex Miller en " Patrones que odio " cita lo siguiente:

"Cuando un singleton parece ser la respuesta, a menudo me parece más prudente:

  1. Cree una interfaz y una implementación predeterminada de su singleton
  2. Construya una única instancia de su implementación predeterminada en la "parte superior" de su sistema. Esto puede ser en una configuración de Spring, o en código, o definido de varias formas dependiendo de su sistema.
  3. Pase la instancia única a cada componente que la necesite (inyección de dependencia)
Jacek Szymański
fuente
2
Sé que esto tiene casi 5 años, pero ¿podrías ampliarlo? Creo que me enseñaron que la forma correcta de crear un singleton era con una interfaz. Me interesaría si lo que estaba haciendo estuviera "bien" y no tan terrible como me han dicho.
Pureferret
97

Para comprender la forma correcta de solucionar los Singleton, debe comprender qué está mal con los Singleton (y el estado global en general):

Los singleton ocultan dependencias.

¿Por qué es tan importante?

Porque si oculta las dependencias, tiende a perder de vista la cantidad de acoplamiento.

Podrías argumentar que

void purchaseLaptop(String creditCardNumber, int price){
  CreditCardProcessor.getInstance().debit(creditCardNumber, amount);
  Cart.getInstance().addLaptop();
}

es más simple que

void purchaseLaptop(CreditCardProcessor creditCardProcessor, Cart cart, 
                    String creditCardNumber, int price){
  creditCardProcessor.debit(creditCardNumber, amount);
  cart.addLaptop();
}

pero al menos la segunda API deja en claro exactamente cuáles son los colaboradores del método.

Por lo tanto, la forma de solucionar los Singletons no es utilizar variables estáticas o localizadores de servicios, sino cambiar las clases Singleton en instancias, que se instancian en el ámbito donde tienen sentido y se inyectan en los componentes y métodos que los necesitan. Puede usar un marco de IoC para manejar esto, o puede hacerlo manualmente, pero lo importante es deshacerse de su estado global y hacer explícitas las dependencias y colaboraciones.

Rasmus Faber
fuente
+1 Por discutir la raíz del problema, no solo cómo solucionarlo.
Phil
9
En una aplicación completa de varios niveles, el segundo método a menudo crea una gran cantidad de código innecesario. Contaminando el código de los niveles intermedios con lógica para pasar a los niveles inferiores, objetos que de otra manera serían innecesarios en ese nivel, ¿no estamos creando dependencias que no deberían serlo? Digamos que el nivel 4 en una pila de navegación inicia una acción en segundo plano, como la carga de un archivo. Ahora, digamos que desea alertar al usuario cuando finalice, pero para entonces, el usuario podría estar en una parte totalmente diferente de la aplicación que solo comparte el nivel 1 en común con la vista inicial. Toneladas de código innecesario ...
Hari Karam Singh
1
@RasmusFaber Un patrón Singleton no oculta las dependencias. Su queja sobre la ocultación de dependencias es una preocupación separada del patrón. Es cierto que el patrón facilita cometer este error, pero es muy posible ejecutar el patrón IoC y Singleton juntos en armonía. Por ejemplo, puedo crear una biblioteca de terceros que necesite una administración especial del ciclo de vida de sus instancias, y cualquiera que use mi biblioteca debería "pasar" una instancia de mi singleton a sus clases usando la inyección de dependencia clásica en lugar de "sky hook" mi singleton desde cada pointcut.
Martin Bliss
@HariKaramSingh puede usar el patrón de suscripción / publicación o el bus de eventos de todo el sistema (que debe compartirse entre todas las partes interesadas, no un singleton) para solucionar este problema.
Victor Sorokin
4
Además, los patrones de pub / sub u observer pueden ser tan malos como "perder la pista del acoplamiento" como cualquier persona que haya tenido que depurar un marco que depende en gran medida de ellos puede atestiguar. Al menos con singleton, el nombre del método que se invoca está en el mismo lugar que el código de invocación :) Personalmente, creo que todas estas estrategias tienen su lugar y ninguna se puede implementar a ciegas. Los programadores descuidados harán espaguetis a partir de cualquier patrón y los programadores responsables no necesitan cargarse excesivamente con limitaciones ideológicas para crear un código elegante.
Hari Karam Singh
14

La mejor solución que he encontrado es usar el patrón de fábrica para construir instancias de sus clases. Al usar el patrón, puede asegurarse de que solo hay una instancia de una clase que se comparte entre los objetos que la usan.

Pensé que sería complicado de administrar, pero después de leer esta publicación de blog "¿Dónde se han ido todos los solteros?" , parece tan natural. Además, ayuda mucho a aislar las pruebas unitarias.

En resumen, ¿qué debes hacer? Siempre que un objeto depende de otro, recibirá una instancia de él solo a través de su constructor (no hay una palabra clave nueva en su clase).

class NeedyClass {

    private ExSingletonClass exSingleton;

    public NeedyClass(ExSingletonClass exSingleton){
        this.exSingleton = exSingleton;
    }

    // Here goes some code that uses the exSingleton object
}

Y luego, la fábrica.

class FactoryOfNeedy {

    private ExSingletonClass exSingleton;

    public FactoryOfNeedy() {
        this.exSingleton = new ExSingletonClass();
    }

    public NeedyClass buildNeedy() {
        return new NeedyClass(this.exSingleton);
    }
}

Como solo creará una instancia de su fábrica, habrá una única instancia de exSingleton. Cada vez que llame a buildNeedy, la nueva instancia de NeedyClass se incluirá con exSingleton.

Espero que esto ayude. Señale cualquier error.

seuvitor
fuente
5
para asegurarse de que se instancia su Factory solo una vez que necesite un constructor privado y defina el método buildNeedy como un método estático.
Julien Grenier
16
Julien tiene razón, esta solución tiene un defecto fundamental, que es que implícitamente está diciendo que solo puede crear una instancia de una fábrica. Si toma las precauciones necesarias para asegurarse de que solo se instancia una fábrica (como dijo Julien), terminará con ... ¡un singleton! En realidad, este enfoque solo agrega una capa innecesaria de abstracción sobre el singleton.
Bob Somers
Hay otra forma de tener solo una instancia. Puede crear una instancia de su fábrica solo una vez. No es necesario que el compilador lo imponga. Esto también ayuda con las pruebas unitarias en las que desea una instancia diferente.
Viernes
Esta es la mejor solución que he visto para el problema que se resuelve incorrectamente con singletons: agregar dependencias sin saturar cada llamada de método (y, por lo tanto, rediseñar la interfaz y refactorizar sus dependencias). Una pequeña modificación es perfecta para mi situación en la que no es importante que solo haya una instancia del "singleton", pero es importante que todos compartan la misma instancia de forma predeterminada (la modificación es: en lugar de usar una fábrica, use static métodos para establecer externamente la instancia común y usar esa instancia durante la construcción).
meustrus
Entonces, básicamente, la ventaja de este enfoque es que solo tiene que barajar una instancia de objeto (la fábrica) en lugar de potencialmente un montón de objetos (para los cuales elige no usar Singletons).
Hari Karam Singh
9

Spring o cualquier otro IoC-Container hace un trabajo razonablemente bueno en eso. Dado que las clases se crean y administran fuera de la aplicación, el contenedor puede hacer clases simples singletons e inyectarlas donde sea necesario.

Kai
fuente
¿Tiene enlaces sobre los temas antes mencionados?
CodingWithoutComments
Estos marcos parecen específicos de Java, ¿alguna otra opción específica del idioma? ¿O agnóstico del idioma?
CodingWithoutComments
para .NET: Castle Windsor castleproject.org/container/index.html Microsoft Unity msdn.microsoft.com/en-us/library/cc468366.aspx Unity es en realidad un marco de inyección de dependencia, pero probablemente funcionará para este tema.
Erik van Brakel
1
¿Por qué no hay demasiados votos para esto? IOC es la solución perfecta para evitar Singleton.
Sandeep Jindal
@CodingWithoutComments Sé que PHP tiene el contenedor de servicios Symfony. Sin embargo, los tipos ruby ​​tienen otras formas de inyectar dependencias. ¿Tiene un idioma específico que le interesa?
Bevelopper
9

No debería tener que salir de su camino para evitar cualquier patrón. El uso de un patrón es una decisión de diseño o un ajuste natural (simplemente encaja en su lugar). Cuando diseña un sistema, tiene la opción de usar un patrón o no usarlo. Sin embargo, no debe salir de su camino para evitar cualquier cosa que, en última instancia, sea una elección de diseño.

No evito el patrón Singleton. O es apropiado y lo uso o no es apropiado y no lo uso. Creo que es tan simple como eso.

La idoneidad (o falta de ella) del Singleton depende de la situación. Es una decisión de diseño que debe tomarse y las consecuencias de esa decisión deben entenderse (y documentarse).

Thomas Owens
fuente
Iba a decir lo mismo, pero no responde del todo a su pregunta :)
Swati
2
En su respuesta, mencione cuándo es apropiado o no. Bastante por favor.
CodingWithoutComments
Responde la pregunta. No tengo técnicas para evitar el uso del Patrón Singleton porque no me esfuerzo en evitarlo, y no creo que ningún desarrollador deba hacerlo.
Thomas Owens
No creo que se pueda generalizar cuando es apropiado o no. Todo es específico del proyecto y depende en gran medida del diseño del sistema en cuestión. No utilizo "reglas generales" para decisiones como esta.
Thomas Owens
Hmm. No entiendo por qué se rechaza esto. Respondí a la pregunta: ¿Cuáles son sus técnicas para evitar el uso del patrón Singleton? - diciendo que no tengo técnicas porque no salgo de mi camino para evitar el patrón. ¿Podrían los votantes en contra ampliar su razonamiento?
Thomas Owens
5

Monostate (descrito en Agile Software Development de Robert C. Martin) es una alternativa al singleton. En este patrón, los datos de la clase son todos estáticos, pero los captadores / definidores no son estáticos.

Por ejemplo:

public class MonoStateExample
{
    private static int x;

    public int getX()
    {
        return x;
    }

    public void setX(int xVal)
    {
        x = xVal;
    }
}

public class MonoDriver
{
    public static void main(String args[])
    {
        MonoStateExample m1 = new MonoStateExample();
        m1.setX(10);

        MonoStateExample m2 = new MonoStateExample();
        if(m1.getX() == m2.getX())
        {
            //singleton behavior
        }
    }
}

Monostate tiene un comportamiento similar al singleton, pero lo hace de una manera en la que el programador no es necesariamente consciente del hecho de que se está utilizando un singleton.

Mark M
fuente
Esto merece más votos; ¡Vine aquí desde Google en busca de un repaso de MonoState!
mahemoff
@Mark Me parece que este diseño es muy difícil de bloquear en términos de seguridad de hilos. ¿Creo un bloqueo de instancia para cada variable estática en MonoStateExample, o creo un bloqueo que aprovechan todas las propiedades? Ambos tienen serias ramificaciones que se resuelven fácilmente si se ejecutan en un patrón Singleton.
Martin Bliss
Este ejemplo es una alternativa al patrón Singleton, pero no resuelve los problemas del patrón Singleton.
Sandeep Jindal
4

El patrón singleton existe porque hay situaciones en las que se necesita un solo objeto para proporcionar un conjunto de servicios .

Incluso si este es el caso, sigo considerando inapropiado el enfoque de crear singleton mediante el uso de un campo / propiedad estática global que representa la instancia. Es inapropiado porque crea una dependencia en el código entre el campo estático y el objeto, no los servicios que proporciona el objeto.

Entonces, en lugar del patrón clásico de singleton, recomiendo usar el patrón de servicio 'like' con contenedores con servicio , donde en lugar de usar su singleton a través de un campo estático, obtiene una referencia a él a través de un método que solicita el tipo de servicio requerido.

*pseudocode* currentContainer.GetServiceByObjectType(singletonType)
//Under the covers the object might be a singleton, but this is hidden to the consumer.

en lugar de un solo global

*pseudocode* singletonType.Instance

De esta manera, cuando desee cambiar el tipo de un objeto de singleton a otro, tendrá un tiempo fácil para hacerlo. Además, como beneficio adicional, no tiene que pasar una gran cantidad de instancias de objetos a cada método.

Consulte también Inversión de control , la idea es que al exponer los singleton directamente al consumidor, cree una dependencia entre el consumidor y la instancia del objeto, no los servicios de objeto proporcionados por el objeto.

Mi opinión es ocultar el uso del patrón singleton siempre que sea posible, porque no siempre es posible, o deseable, evitarlo.

Pop Catalin
fuente
No sigo su ejemplo de contenedores reparados. ¿Tiene recursos en línea a los que pueda vincular?
CodingWithoutComments
Eche un vistazo a "Castle Project - Windsor Container" castleproject.org/container/index.html . Desafortunadamente, es muy difícil encontrar publicaciones abstractas sobre este tema.
Pop Catalin
2

Si está utilizando un Singleton para representar un único objeto de datos, podría pasar un objeto de datos como parámetro de método.

(aunque, en primer lugar, diría que esta es la forma incorrecta de usar un Singleton)

David Koelle
fuente
1

Si su problema es que desea mantener el estado, desea una clase MumbleManager. Antes de comenzar a trabajar con un sistema, su cliente crea un MumbleManager, donde Mumble es el nombre del sistema. El estado se conserva a través de eso. Lo más probable es que su MumbleManager contenga una bolsa de propiedades que contenga su estado.

Este tipo de estilo se siente muy parecido a C y no muy parecido a un objeto: encontrará que los objetos que definen su sistema tendrán todos una referencia al mismo MumbleManager.

pedestal
fuente
Sin defender ni a favor ni en contra de Singleton, debo decir que esta solución es un anti-patrón. MumbleManager se convierte en una "clase de Dios" que contiene todo tipo de conocimientos dispares, violando la Responsabilidad Única. También puede ser un Singleton para todos los cuidados de la aplicación.
Martin Bliss
0

Utilice un objeto simple y un objeto de fábrica. La fábrica es responsable de vigilar la instancia y los detalles del objeto simple solo con la información de configuración (que contiene, por ejemplo) y el comportamiento.

David
fuente
1
¿No es una fábrica a menudo un singleton? Así es como suelo ver implementados los patrones de Factory.
Thomas Owens
0

En realidad, si diseña desde cero para evitar Singeltons, es posible que no tenga que evitar no usar Singletons mediante el uso de variables estáticas. Cuando usas variables estáticas, también estás creando un Singleton más o menos, la única diferencia es que estás creando diferentes instancias de objetos, sin embargo internamente todos se comportan como si estuvieran usando un Singleton.

¿Puede dar un ejemplo detallado en el que utiliza un Singleton o donde se utiliza actualmente un Singleton y está tratando de evitar su uso? Esto podría ayudar a las personas a encontrar una solución más elegante de cómo se podría manejar la situación sin un Singleton.

Por cierto, personalmente no tengo problemas con Singletons y no puedo entender los problemas que tienen otras personas con respecto a Singletons. No veo nada malo en ellos. Es decir, si no está abusando de ellos. Se puede abusar de todas las técnicas útiles y, si se abusa, se producirán resultados negativos. Otra técnica que se utiliza habitualmente de forma incorrecta es la herencia. Aún así, nadie diría que la herencia es algo malo solo porque algunas personas abusan horriblemente de ella.

Mecki
fuente
0

Personalmente, para mí, una forma mucho más sensata de implementar algo que se comporta como singleton es usar una clase completamente estática (miembros estáticos, métodos estáticos, propiedades estáticas). La mayoría de las veces lo implemento de esta manera (no puedo pensar en ninguna diferencia de comportamiento desde el punto de vista del usuario)

user18834
fuente
0

Creo que el mejor lugar para vigilar al singleton es el nivel de diseño de la clase. En esta etapa, debería poder trazar las interacciones entre clases y ver si algo absolutamente, definitivamente requiere que solo una instancia de esta clase exista en algún momento de la vida de la aplicación.

Si ese es el caso, entonces tienes un singleton. Si está lanzando singletons como una conveniencia durante la codificación, entonces realmente debería revisar su diseño y también dejar de codificar dichos singleton :)

Y sí, 'policía' es la palabra a la que me refería aquí en lugar de 'evitar'. El singleton no es algo que deba evitarse (de la misma manera que las variables goto y globales no son algo que deba evitarse). En su lugar, debe monitorear su uso y asegurarse de que sea el mejor método para hacer lo que desea de manera efectiva.

workmad3
fuente
1
Entiendo totalmente lo que estás diciendo workmad. Y estoy completamente de acuerdo. Sin embargo, su respuesta todavía no responde específicamente a mi pregunta. No quería que esta fuera otra cuestión de "¿cuándo es apropiado usar el singleton?" Solo estaba interesado en alternativas viables al singleton.
CodingWithoutComments
0

Utilizo singleton principalmente como "contenedor de métodos", sin ningún estado. Si necesito compartir estos métodos con muchas clases y quiero evitar la carga de la creación de instancias e inicialización, creo un contexto / sesión e inicializo todas las clases allí; todo lo que se refiere a la sesión también tiene acceso al "singleton" que contiene.

Manrico Corazzi
fuente
0

No habiendo programado en un entorno intensamente orientado a objetos (por ejemplo, Java), no estoy completamente al tanto de las complejidades de la discusión. Pero he implementado un singleton en PHP 4. Lo hice como una forma de crear un manejador de base de datos de 'caja negra' que se inicializaba automáticamente y no tenía que pasar llamadas de función arriba y abajo en un marco incompleto y algo roto.

Después de leer algunos enlaces de patrones singleton, no estoy completamente seguro de que lo implementaría de la misma manera nuevamente. Lo que realmente se necesitaba eran varios objetos con almacenamiento compartido (por ejemplo, el identificador de la base de datos real) y esto es más o menos en lo que se convirtió mi llamada.

Como la mayoría de los patrones y algoritmos, usar un singleton 'solo porque es genial' es lo incorrecto. Necesitaba una llamada verdaderamente de "caja negra" que se pareciera mucho a un singleton. Y en mi opinión, esa es la forma de abordar la pregunta: tenga en cuenta el patrón, pero también observe su alcance más amplio y en qué nivel su instancia debe ser única.

estático
fuente
-1

¿Qué quieres decir con cuáles son mis técnicas para evitarlo?

Para "evitarlo", eso implica que hay muchas situaciones con las que me encuentro en las que el patrón singleton encaja naturalmente bien y, por lo tanto, tengo que tomar algunas medidas para desactivar estas situaciones.

Pero no los hay. No tengo que evitar el patrón singleton. Simplemente no surge.

DrPizza
fuente