2010-04-18 16 views
57

¿Cómo identificamos cuándo utilizar la inyección de dependencia o el patrón de singleton? He leído en muchos sitios web donde dicen "Use Dependency injection over singleton pattern". Pero no estoy seguro si estoy totalmente de acuerdo con ellos. Para mis proyectos de pequeña o mediana escala, definitivamente veo el uso del patrón singleton directo.Patrón de Inyección de Dependencia y Diseño Único

Por ejemplo, registrador. Podría usar Logger.GetInstance().Log(...) Pero, en lugar de esto, ¿por qué tengo que inyectar todas las clases que creo, con la instancia del registrador ?.

Respuesta

50

Si desea verificar qué se registra en una prueba, necesita una inyección de dependencia. Además, un registrador rara vez es un singleton; generalmente tiene un registrador por cada clase.

Watch this presentation en Diseño orientado a objetos para la comprobación y verá por qué los singleton son malos.

El problema con los singleton es que representan un estado global que es difícil de predecir, especialmente en las pruebas.

Tenga en cuenta que un objeto puede ser un singleton de facto pero aún se puede obtener a través de la inyección de dependencia, en lugar de a través del Singleton.getInstance().

Acabo de enumerar algunos puntos importantes hechos por Misko Hevery en su presentación. Después de verlo obtendrá una perspectiva completa de por qué es mejor tener un objeto definir cuáles son sus dependencias, pero no definir una forma cómo crearlas.

+0

Soy nuevo en este diseño pattren, y me gustaría saber por qué quieres decir con "prueba" cuando dices especialmente en las pruebas? gracias de antemano. – AnixPasBesoin

+0

Me refiero a pruebas de unidad/integración – Bozho

7

En su mayoría, pero no en todas las pruebas. Singltons era popular porque era fácil consumirlos, pero hay una serie de inconvenientes para los singletons.

  • Hard to test. Es decir, cómo me aseguro de que el registrador haga lo correcto.
  • Es difícil de probar. Es decir, si estoy probando el código que usa el registrador, pero no es el objetivo de mi prueba, aún necesito asegurarme de que mi entorno de prueba sea compatible con el registrador.
  • A veces no desea una canción, pero es más flexible

DI le ofrece el fácil consumo de sus clases dependientes, simplemente colóquelo en los argumentos del constructor, y el sistema lo proporciona para usted, mientras le da la flexibilidad de prueba y construcción.

+0

No realmente. Se trata del estado mutable compartido y las dependencias estáticas que hacen que las cosas sean dolorosas a largo plazo. Las pruebas son solo el ejemplo obvio, y con frecuencia el más doloroso. – kyoryu

72

Los singletons son como el comunismo: ambos suenan muy bien en papel, pero explotan con problemas en la práctica.

El patrón singleton pone un énfasis desproporcionado en la facilidad de acceso a los objetos. Evita completamente el contexto al exigir que cada consumidor use un objeto con ámbito AppDomain, sin dejar opciones para implementaciones variables. Incorpora conocimiento de infraestructura en sus clases (la llamada a GetInstance()) mientras agrega exactamente cero poder expresivo. En realidad, disminuye su poder expresivo, porque no puede cambiar la implementación utilizada por una clase sin cambiarla por todos de ellos. Simplemente no puede agregar piezas únicas de funcionalidad.

Además, cuando la clase Foo depende de Logger.GetInstance(), Foo está ocultando efectivamente sus dependencias de los consumidores.Esto significa que no puede comprender completamente Foo o usarlo con confianza a menos que lea su fuente y descubra el hecho de que depende de Logger. Si no tiene la fuente, eso limita lo bien que puede entender y usar efectivamente el código del que depende.

El patrón singleton, implementado con propiedades/métodos estáticos, es poco más que un truco para implementar una infraestructura. Te limita de muchas maneras sin ofrecer un beneficio discernible sobre las alternativas. Puede usarlo como lo desee, pero como existen alternativas viables que promueven un mejor diseño, nunca debe ser una práctica recomendada.

+5

@BryanWatts todo lo dicho, los Singleton son aún mucho más rápidos y menos propensos a errores en una aplicación normal de mediana escala. Por lo general, no tengo más de una implementación posible para un registrador (personalizado) así que ¿por qué ** necesito: 1. Crear una interfaz para eso y cambiarla cada vez que necesito agregar/cambiar miembros públicos 2. Mantener una configuración DI 3. Ocultar el hecho de que hay un solo objeto de este tipo en todo el sistema 4. Vincúlate a una funcionalidad estricta causada por una Separación de preocupaciones prematura. –

+0

@UriAbramson: Parece que ya has tomado la decisión sobre las compensaciones que prefieres, así que no intentaré convencerte. –

+0

@BryanWatts No es el caso, tal vez mi comentario fue demasiado duro, pero en realidad me interesa mucho escuchar lo que tienes que decir sobre el punto que mencioné ... –

13

Otros han explicado muy bien el problema con los singletons en general. Me gustaría agregar una nota sobre el caso específico de Logger. Estoy de acuerdo con usted en que generalmente no es un problema acceder a un registrador (o al registrador de raíz, para ser precisos) como un singleton, a través de un método estático getInstance() o getRootLogger(). (a menos que quiera ver lo que registra la clase que está probando, pero en mi experiencia difícilmente puedo recordar casos en los que fue necesario. De nuevo, para otros esto podría ser una preocupación más apremiante).

IMO generalmente un registrador de singleton no es una preocupación, ya que no contiene ningún estado relevante para la clase que está probando. Es decir, el estado del registrador (y sus posibles cambios) no tienen ningún efecto en el estado de la clase probada. Por lo tanto, no hace que las pruebas de su unidad sean más difíciles.

La alternativa sería inyectar el registrador a través del constructor, a (casi) cada clase en su aplicación. Para la coherencia de las interfaces, se debe inyectar incluso si la clase en cuestión no registra nada en la actualidad; la alternativa sería que cuando descubras en algún momento que ahora necesitas registrar algo de esta clase, necesitas un registrador , por lo tanto, necesita agregar un parámetro de constructor para DI, rompiendo todo el código del cliente. No me gustan estas dos opciones, y creo que usar DI para registrar me complicaría la vida solo para cumplir con una regla teórica, sin ningún beneficio concreto.

Así que mi conclusión es: una clase que se utiliza (casi) universalmente, pero no contiene un estado relevante para su aplicación, se puede implementar de forma segura como Singleton.

+1

Incluso entonces, un singleton puede ser un problema. Si se da cuenta de que su registrador necesita algún parámetro adicional para algunos casos, puede hacer un nuevo método (y dejar que el registrador se vuelva más feo), o romper todos los consumidores del registrador, incluso si no les importa. – kyoryu

+2

@kyoryu, estoy hablando del caso "habitual", que en mi humilde opinión implica el uso de un marco de registro estándar (de facto). (Que típicamente son configurables vía propiedad/archivos XML por cierto). Por supuesto que hay excepciones, como siempre. Si sé que mi aplicación es excepcional a este respecto, no utilizaré un Singleton de hecho.Pero el exceso de ingeniería que dice "esto podría ser útil alguna vez" es casi siempre un esfuerzo desperdiciado. –

+0

Si ya usa DI, no es una ingeniería extra. (Por cierto, no estoy en desacuerdo contigo, soy el votante). Muchos madereros requieren cierta información de "categoría" o algo por el estilo, y agregar parámetros adicionales puede causar dolor. Ocultarlo detrás de una interfaz puede ayudar a mantener limpio el código de consumo y puede facilitar el cambio a un marco de trabajo de registro diferente. – kyoryu

1

La única vez que debe usar un Singleton en lugar de Inyección de Dependencia es si Singleton representa un valor inmutable, como List.Empty o similar (asumiendo listas inmutables).

La comprobación de un singleton debería ser "¿estaría bien si fuera una variable global en lugar de un Singleton?" Si no, estás usando el patrón de Singleton para escribir sobre una variable global, y debes considerar un enfoque diferente.

1

acaba de comprobar el artículo de Monostate - es una alternativa ingeniosa a Singleton, pero tiene algunas propiedades extrañas:

class Mono{ 
    public static $db; 
    public function setDb($db){ 
     self::$db = $db; 
    } 

} 

class Mapper extends Mono{ 
    //mapping procedure 
    return $Entity; 

    public function save($Entity);//requires database connection to be set 
} 

class Entity{ 
public function save(){ 
    $Mapper = new Mapper(); 
    $Mapper->save($this);//has same static reference to database class  
} 

$Mapper = new Mapper(); 
$Mapper->setDb($db); 

$User = $Mapper->find(1); 
$User->save(); 

no está presente tipo de miedo - porque el asignador es realmente dependiente de la conexión de la base de datos para realizar guardar() - pero si otro mapper ha sido creado previamente; puede omitir este paso al adquirir sus dependencias. Si bien limpio, también es un poco complicado, ¿no?

Cuestiones relacionadas