Alternativas al patrón singleton


27

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

  1. 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)
  2. 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 .


8
Inyección de dependencia ...
Falcon

1
@ Falcon: Inyección de dependencia ... ¿qué? DI no hace nada para resolver este problema, aunque a menudo le brinda una forma práctica de ocultarlo.
pdr

@ Falcon, ¿te refieres al contenedor IoC? Esto le permitirá resolver dependencias en una sola línea. Ej. Dependencia. Resuelve <IFoo> (); o Dependencia. Resuelva <IFoo> () .DoSomething ();
CodeART

Esta es una pregunta bastante antigua, y ya respondida. Todavía pienso que sería bueno vincular este: stackoverflow.com/questions/162042/…
TheSilverBullet

Respuestas:


7

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.


17

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:

  • instancias locales de corta duración, por ejemplo, para una fábrica: las clases típicas de fábrica tienen cantidades mínimas de estado, si las hay, y no hay ninguna razón real para mantenerlas con vida después de haber cumplido su propósito; La sobrecarga de crear y eliminar clases no es nada de qué preocuparse en el 99% de todos los escenarios del mundo real
  • pasar una instancia, por ejemplo, para un administrador de recursos: estos tienen que ser de larga duración, porque cargar recursos es costoso y desea mantenerlos en la memoria, pero no hay absolutamente ninguna razón para evitar que se creen nuevas instancias, quién sabe, tal vez tenga sentido tener un segundo administrador de recursos dentro de unos meses ...

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.


3
Esta es una muy buena respuesta, pero realmente no estaba preguntando por qué debería / no debería usar el patrón singleton. Sé los problemas que uno puede tener con el estado global. De todos modos, el tema de la pregunta era cómo tener objetos únicos y, sin embargo, una implementación sin patrón único.
Giorgio

3

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ó.


¿Quiere decir que la inyección de dependencia de la idea básica es básicamente el primer enfoque que bosquejé anteriormente (por ejemplo, usando un registro), por fin la idea básica?
Giorgio

@Giorgio: DI siempre incluye dicho registro, pero el punto principal que lo mejora es que, en lugar de consultar el registro donde sea que necesite un objeto, tiene los objetos centrales que se rellenan de forma transitiva, como escribí anteriormente.
Michael Borgwardt

@Giorgio: es más fácil de entender con un ejemplo concreto: supongamos que tiene una aplicación Java EE. Su lógica de front-end está en un método de acción de un JSF Managed Bean. Cuando el usuario presiona un botón, el contenedor JSF crea una instancia del bean administrado y llama al método de acción. Pero antes de eso, el componente DI mira los campos de la clase Managed Bean, y aquellos que tienen una cierta anotación se rellenan con una referencia a un objeto de Servicio según la configuración de DI, y esos objetos de Servicio les están sucediendo lo mismo. . El método de acción puede llamar a los servicios.
Michael Borgwardt

Algunas personas (incluido usted) no leen las preguntas con cuidado y no entienden lo que realmente se les pregunta.
Giorgio

1
@Giorgio: nada en su pregunta original indica que ya está familiarizado con la inyección de dependencia. Y ver la respuesta actualizada.
Michael Borgwardt

3

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í:

GetSomeManager()->GetRootManager()->GetAnotherManager()->DoActualThingICareAbout()

Además de burlarse, ¿qué ventaja tiene DI sobre el uso de un singleton? Esta es una pregunta que me ha molestado mucho tiempo. En cuanto al registro y al administrador raíz, ¿no se implementan como un singleton o mediante DI? (De hecho, el contenedor de IoC es un registro, ¿verdad?)
Konrad Rudolph

1
@KonradRudolph, con DI las dependencias son explícitas y el objeto no hace suposiciones sobre cómo se suministra un recurso dado. Con los singletons, las dependencias son implícitas y cada uso supone que solo habrá un objeto de este tipo.
Winston Ewert

@KonradRudolph, ¿se implementan los otros métodos usando Singletons o DI? Sí y No. Al final, tienes que pasar objetos o almacenar objetos en estado global. Pero hay diferencias sutiles en cómo hacer esto. Las tres versiones mencionadas anteriormente evitan usar el estado global; pero la forma en que el código final obtiene las referencias es diferente.
Winston Ewert

El uso de singletons no supone un objeto único si el singleton implementa una interfaz (o incluso si no) y pasa la instancia de singleton a métodos, en lugar de consultar Singleton::instanceen todas partes en el código del cliente.
Konrad Rudolph,

@KonradRudolph, entonces realmente estás usando un enfoque híbrido Singleton / DI. Mi lado purista piensa que deberías optar por un enfoque DI puro. Sin embargo, la mayoría de los problemas con singleton realmente no se aplican allí.
Winston Ewert

1

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.

class Factory
{
public:
    static Object* createObject(...);
};

Object* obj = Factory::createObject(...);

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.


44
Personalmente, veo esto como una implementación diferente del patrón singleton. En este caso, la instancia de singleton es la clase misma. Específicamente: tiene los mismos inconvenientes que un singleton "real".
Joachim Sauer

@Randall Cook: Creo que tu respuesta capta mi punto. Leí muy a menudo que el singleton es muy malo y debe evitarse a toda costa. Estoy de acuerdo con esto pero, por otro lado, creo que es técnicamente imposible evitarlo siempre. Cuando necesita "uno de algo", debe crearlo en algún lugar y acceder a él de alguna manera.
Giorgio

1

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.


0

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):

// network info
class Session {
  // ...
};

// current user connection info
class CurrentUser {
  // ...
};

// configuration upon user
class UserConfiguration {
  // ...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfiguration {
  // ...
};

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):

// network info
class NetworkInfoClass {
  // ...
};

// current user connection info
class CurrentUserClass {
  // ...
};

// configuration upon user
// favorite options menues
class UserConfigurationClass {
  // ...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfigurationClass {
  // ...
};

// this class is instantiated as a singleton
class SessionClass {
  public: NetworkInfoClass NetworkInfo;
  public: CurrentUserClass CurrentUser;
  public: UserConfigurationClass UserConfiguration;
  public: TerminalConfigurationClass TerminalConfiguration;

  public: static SessionClass Instance();
};

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.


0

Sus requisitos originales:

Entonces, el escenario general es que

  1. Solo necesito una instancia de una clase, ya sea por razones de optimización (no necesito> múltiples 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)
  2. Necesito una forma de tener acceso a esta instancia f de F en diferentes lugares del código.

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:

Asegúrese de que una clase solo tenga una instancia y proporcione un punto de acceso global.

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.


0

¿Qué tal usar el contenedor IoC? Con un poco de pensamiento, puede terminar con algo en la línea de:

Dependency.Resolve<IFoo>(); 

Tendría que especificar qué implementación IFoousar 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:

Dependency.Resolve<IFoo>().DoSomething();

El método getter de singleton estático podría reemplazarse con:

var result = Dependency.Resolve<IFoo>().GetFoo();

El ejemplo es relevante para .NET, pero estoy seguro de que se puede lograr muy similar en otros idiomas.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.