Si los Singletons son malos, ¿por qué es bueno un Service Container?

91

Todos sabemos lo malos que son los Singleton porque ocultan dependencias y por otras razones .

Pero en un marco, podría haber muchos objetos que deban instanciarse solo una vez y llamarse desde cualquier lugar (logger, db, etc.).

Para resolver este problema, me han dicho que use un "Administrador de objetos" (o un contenedor de servicios como Symfony) que almacena internamente todas las referencias a los servicios (registrador, etc.).

Pero, ¿por qué un proveedor de servicios no es tan malo como un singleton puro?

El proveedor de servicios también oculta las dependencias y simplemente termina la creación de la primera istancia. Así que realmente estoy luchando por entender por qué deberíamos usar un proveedor de servicios en lugar de singletons.

PD. Sé que para no ocultar las dependencias debería usar DI (como dice Misko)

Añadir

Yo agregaría: estos días los singletons no son tan malvados, el creador de PHPUnit lo explicó aquí:

DI + Singleton resuelve el problema:

<?php
class Client {

    public function doSomething(Singleton $singleton = NULL){

        if ($singleton === NULL) {
            $singleton = Singleton::getInstance();
        }

        // ...
    }
}
?>

eso es bastante inteligente, incluso si esto no resuelve todos los problemas.

Aparte de DI y Service Container, ¿hay alguna buena solución aceptable para acceder a estos objetos auxiliares?

dinámica
fuente
2
@yes Tu edición está haciendo suposiciones falsas. Sebastian, de ninguna manera, sugiere que el fragmento de código esté haciendo que el uso de Singleons sea menos problemático. Es solo una forma de hacer código que de otra manera sería imposible probar más comprobable. Pero sigue siendo un código problemático. De hecho, señala explícitamente: "El hecho de que puedas, no significa que debas". La solución correcta aún sería no usar Singletons en absoluto.
Gordon
3
@sí sigue el principio SOLID.
Gordon
19
Disputo la afirmación de que los singletons son malos. Se pueden usar incorrectamente, sí, pero también puede hacerlo cualquier herramienta. Se puede utilizar un bisturí para salvar una vida o acabar con ella. Una motosierra puede despejar bosques para prevenir incendios forestales o puede cortar una parte considerable de su brazo si no sabe lo que está haciendo. Aprenda a usar sus herramientas sabiamente y no trate los consejos como si fueran un evangelio; de esa manera reside la mente irreflexiva.
paxdiablo
4
@paxdiablo pero son malos. Los singleton violan SRP, OCP y DIP. Introducen el estado global y el acoplamiento estrecho en su aplicación y harán que su API mienta sobre sus dependencias. Todo esto afectará negativamente la capacidad de mantenimiento, la legibilidad y la capacidad de prueba de su código. Puede haber casos raros en los que estos inconvenientes superen los pequeños beneficios, pero yo diría que en el 99% no necesita un Singleton. Especialmente en PHP, donde los Singletons son únicos para la Solicitud de todos modos y es muy simple ensamblar gráficos de colaboradores desde un Constructor.
Gordon
5
No, no lo creo. Una herramienta es un medio para llevar a cabo una función, generalmente haciéndola más fácil de alguna manera, aunque algunos (¿emacs?) Tienen la rara distinción de hacerlo más difícil :-) En esto, un singleton no es diferente a un árbol balanceado o un compilador . Si necesita asegurarse solo una copia de un objeto, un singleton lo hace. Se puede debatir si lo hace bien, pero no creo que se pueda argumentar que no lo hace en absoluto. Y puede haber mejores formas, como que una motosierra sea más rápida que una sierra de mano, o una pistola de clavos frente a un martillo. Eso no hace que la sierra de mano / martillo sea menos herramienta.
paxdiablo

Respuestas:

76

Service Locator es solo el menor de dos males, por así decirlo. El "menor" se reduce a estas cuatro diferencias ( al menos no puedo pensar en ninguna otra en este momento ):

Principio de responsabilidad única

Service Container no viola el principio de responsabilidad única como lo hace Singleton. Los singleton combinan la creación de objetos y la lógica empresarial, mientras que Service Container es estrictamente responsable de administrar los ciclos de vida de los objetos de su aplicación. En ese sentido, Service Container es mejor.

Acoplamiento

Los singletons generalmente están codificados en su aplicación debido a las llamadas a métodos estáticos, lo que conduce a dependencias estrechamente acopladas y difíciles de simular en su código. El SL, por otro lado, es solo una clase y se puede inyectar. Entonces, si bien todos sus clasificados dependerán de ello, al menos es una dependencia poco acoplada. Entonces, a menos que haya implementado ServiceLocator como un Singleton, eso es algo mejor y también más fácil de probar.

Sin embargo, todas las clases que usan ServiceLocator ahora dependerán del ServiceLocator, que también es una forma de acoplamiento. Esto se puede mitigar mediante el uso de una interfaz para ServiceLocator para que no esté vinculado a una implementación de ServiceLocator concreta, pero sus clases dependerán de la existencia de algún tipo de localizador, mientras que no usar un ServiceLocator aumenta la reutilización drásticamente.

Dependencias ocultas

Sin embargo, el problema de ocultar las dependencias sigue existiendo. Cuando simplemente inyecta el localizador a sus clases consumidoras, no conocerá ninguna dependencia. Pero a diferencia del Singleton, el SL normalmente instanciará todas las dependencias necesarias detrás de escena. Por lo tanto, cuando obtiene un Servicio, no termina como Misko Hevery en el ejemplo de CreditCard , por ejemplo , no tiene que crear una instancia de todas las dependencias de las dependencias a mano.

Obtener las dependencias desde el interior de la instancia también viola la Ley de Demeter , que establece que no debe indagar en los colaboradores. Una instancia solo debe hablar con sus colaboradores inmediatos. Este es un problema tanto con Singleton como con ServiceLocator.

Estado global

El problema del estado global también se mitiga un poco porque cuando crea una instancia de un nuevo localizador de servicios entre pruebas, también se eliminan todas las instancias creadas anteriormente (a menos que haya cometido el error y las haya guardado en atributos estáticos en el SL). Eso no es cierto para ningún estado global en las clases administradas por el SL, por supuesto.

También vea Fowler en Service Locator vs Dependency Injection para una discusión mucho más profunda.


Una nota sobre su actualización y el artículo vinculado de Sebastian Bergmann sobre el código de prueba que usa Singletons : Sebastian, de ninguna manera, sugiere que la solución propuesta hace que el uso de Singleons sea menos problemático. Es solo una forma de hacer código que de otra manera sería imposible probar más comprobable. Pero sigue siendo un código problemático. De hecho, señala explícitamente: "El hecho de que puedas, no significa que debas".

Gordon
fuente
1
Especialmente la capacidad de prueba debe aplicarse aquí. No puede simular llamadas a métodos estáticos. Sin embargo, puede simular servicios que se inyectaron a través de constructor o setter.
David
44

El patrón del localizador de servicios es un anti-patrón. No resuelve el problema de exponer dependencias (no se puede saber al mirar la definición de una clase cuáles son sus dependencias porque no se están inyectando, sino que se están sacando del localizador de servicios).

Entonces, su pregunta es: ¿por qué son buenos los localizadores de servicios? Mi respuesta es: no lo son.

Evite, evite, evite.

jason
fuente
6
Parece que no sabes nada sobre interfaces. La clase simplemente describe la interfaz necesaria en la firma del constructor, y es todo lo que necesita saber. El localizador de servicios aprobado debería implementar una interfaz, eso es todo. Y si IDE verifica la implementación de la interfaz, será bastante fácil controlar cualquier cambio.
OZ_
4
@ yes123: La gente que dice eso se equivoca y se equivoca porque SL es un anti-patrón. Tu pregunta es "¿por qué SL es bueno?" Mi respuesta es: no lo son.
jason
5
No discutiré si SL es un patrón anit o no, pero lo que diré es que es un mal menor en comparación con los singleton y los globales. No puedes probar una clase que depende de un singleton, pero definitivamente puedes probar una clase que depende de un SL (aunque puedes estropear el diseño SL hasta el punto en que no funcione) ... señalando ...
ircmaxell
3
@Jason, debe pasar el objeto que implementa la interfaz, y es solo lo que necesita saber. Te estás limitando solo por la definición del constructor de clase y quieres escribir en el constructor todas las clases (no interfaces); es una idea estúpida. Todo lo que necesita es la interfaz. Puede probar con éxito esta clase con simulacros, puede cambiar fácilmente el comportamiento sin cambiar el código, no hay dependencias ni acoplamientos adicionales; eso es todo (en general) lo que queremos tener en la inyección de dependencia.
OZ_
2
Claro, simplemente juntaré Base de datos, Registrador, Disco, Plantilla, Caché y Usuario en un único objeto de "Entrada", seguramente será más fácil saber en qué dependencias depende mi objeto que si hubiera usado un contenedor.
Mahn
4

El contenedor de servicios oculta las dependencias como lo hace el patrón Singleton. Es posible que desee sugerir el uso de contenedores de inyección de dependencia en su lugar, ya que tiene todas las ventajas del contenedor de servicios, pero no tiene (hasta donde yo sé) desventajas que tiene el contenedor de servicios.

Hasta donde yo lo entiendo, la única diferencia entre los dos es que en el contenedor de servicios, el contenedor de servicios es el objeto que se está inyectando (ocultando así las dependencias), cuando usa DIC, el DIC inyecta las dependencias apropiadas para usted. La clase administrada por el DIC es completamente ajena al hecho de que está administrada por un DIC, por lo que tiene menos acoplamiento, dependencias claras y pruebas unitarias felices.

Esta es una buena pregunta en SO que explica la diferencia de ambos: ¿Cuál es la diferencia entre los patrones de Inyección de dependencia y Localizador de servicios?

Rickchristie
fuente
"el DIC inyecta las dependencias apropiadas para usted" ¿No sucede esto también con Singleton?
dinámico
5
@ yes123 - Si está usando un Singleton, no lo inyectaría, la mayoría de las veces simplemente accedería a él globalmente (ese es el punto de Singleton). Supongo que si dices que si inyectas el Singleton, no ocultará las dependencias, pero en cierto modo anula el propósito original del patrón Singleton; te preguntarías, si no necesito que se acceda a esta clase globalmente, por qué ¿Necesito hacerlo Singleton?
rickchristie
2

Porque puede reemplazar fácilmente objetos en Service Container por
1) herencia (la clase Object Manager se puede heredar y los métodos se pueden anular)
2) cambiar la configuración (en el caso de Symfony)

Y, los Singleton son malos no solo por el alto acoplamiento, sino porque son _ Single _ton. Es una arquitectura incorrecta para casi todo tipo de objetos.

Con DI 'puro' (en constructores) pagará un precio muy alto: todos los objetos deben crearse antes de pasar al constructor. Significará más memoria utilizada y menos rendimiento. Además, no siempre el objeto se puede crear y pasar en el constructor - se puede crear una cadena de dependencias ... Mi inglés no es lo suficientemente bueno para discutir sobre eso completamente, lea sobre ello en la documentación de Symfony.

ONZ_
fuente
0

Para mí, trato de evitar las constantes globales, singletons por una simple razón, hay casos en los que podría necesitar que se ejecuten las API.

Por ejemplo, tengo front-end y admin. Dentro del administrador, quiero que puedan iniciar sesión como usuario. Considere el código dentro de admin.

$frontend = new Frontend();
$frontend->auth->login($_GET['user']);
$frontend->redirect('/');

Esto puede establecer una nueva conexión de base de datos, un nuevo registrador, etc. para la inicialización de la interfaz y verificar si el usuario realmente existe, si es válido, etc. También usaría los servicios de ubicación y cookies separados adecuados.

Mi idea de singleton es: no puede agregar el mismo objeto dentro del padre dos veces. Por ejemplo

$logger1=$api->add('Logger');
$logger2=$api->add('Logger');

te dejaría con una sola instancia y ambas variables apuntando a ella.

Por último, si desea utilizar el desarrollo orientado a objetos, trabaje con objetos, no con clases.

romaninsh
fuente
1
¿Entonces su método es pasar la $api var alrededor de su marco? No entendí exactamente lo que quieres decir. Además, si la llamada add('Logger')devuelve la misma instancia, básicamente, tiene un cliente de servicio
dinámico
sí es cierto. Me refiero a ellos como "Controlador del sistema" y están destinados a mejorar la funcionalidad de la API. De manera similar, agregar un controlador "Auditable" a un modelo dos veces funcionaría exactamente de la misma manera: crear solo una instancia y solo un conjunto de campos de auditoría.
Romaninsh