Al diseñar un sistema, a menudo me enfrento al problema de que los otros módulos utilicen un montón de módulos (registro, acceso a la base de datos, etc.). La pregunta es, ¿cómo hago para proporcionar estos componentes a otros componentes? Dos respuestas parecen posibles inyección de dependencia o utilizando el patrón de fábrica. Sin embargo, ambos parecen equivocados:
- Las fábricas dificultan las pruebas y no permiten el intercambio fácil de implementaciones. Tampoco hacen aparentes las dependencias (por ejemplo, está examinando un método, ajeno al hecho de que llama a un método que llama a un método que llama a un método que utiliza una base de datos).
- La inyección de dependencia aumenta enormemente las listas de argumentos del constructor y difumina algunos aspectos en todo el código. La situación típica es donde los constructores de más de medias clases se ven así
(....., LoggingProvider l, DbSessionProvider db, ExceptionFactory d, UserSession sess, Descriptions d)
Aquí hay una situación típica con la que tengo un problema: tengo clases de excepción, que usan descripciones de error cargadas de la base de datos, usando una consulta que tiene un parámetro de configuración de idioma de usuario, que está en el objeto de sesión de usuario. Entonces, para crear una nueva excepción, necesito una descripción, que requiere una sesión de base de datos y la sesión de usuario. Así que estoy condenado a arrastrar todos estos objetos a través de todos mis métodos en caso de que necesite lanzar una excepción.
¿Cómo abordo tal problema?
Respuestas:
Utilice la inyección de dependencia, pero cada vez que las listas de argumentos de su constructor se vuelvan demasiado grandes, refactorícelas utilizando un Servicio de fachada . La idea es agrupar algunos de los argumentos del constructor, introduciendo una nueva abstracción.
Por ejemplo, podría introducir un nuevo tipo
SessionEnvironment
encapsulando aDBSessionProvider
, elUserSession
y el cargadoDescriptions
. Sin embargo, para saber qué abstracciones tienen más sentido, uno tiene que conocer los detalles de su programa.Ya se hizo una pregunta similar aquí sobre SO .
fuente
A partir de eso, no parece que entiendas DI correctamente: la idea es invertir el patrón de creación de instancias de objetos dentro de una fábrica.
Su problema específico parece ser un problema OOP más general. ¿Por qué los objetos no pueden simplemente lanzar excepciones normales, no legibles por humanos durante su tiempo de ejecución, y luego tener algo antes del último intento / captura que atrapa esa excepción, y en ese punto usa la información de la sesión para lanzar una nueva excepción más bonita? ?
Otro enfoque sería tener una fábrica de excepciones, que se pasa a los objetos a través de sus constructores. En lugar de lanzar una nueva excepción, la clase puede lanzar un método de fábrica (p
throw PrettyExceptionFactory.createException(data)
. Ej .Tenga en cuenta que sus objetos, aparte de sus objetos de fábrica, nunca deben usar el
new
operador. Las excepciones son generalmente el único caso especial, ¡pero en su caso podrían ser una excepción!fuente
Builder
patrón) lo dicta Si está pasando parámetros a su constructor porque su objeto crea instancias de otros objetos, ese es un caso claro para IoC.Ya ha enumerado las desventajas del patrón estático de fábrica bastante bien, pero no estoy del todo de acuerdo con las desventajas del patrón de inyección de dependencia:
Esa inyección de dependencias requiere que escribas código para cada dependencia no es un error, sino una característica: te obliga a pensar si realmente necesitas estas dependencias, promoviendo así un acoplamiento flexible. En tu ejemplo:
No, no estás condenado. ¿Por qué es responsabilidad de la lógica empresarial localizar sus mensajes de error para una sesión de usuario en particular? ¿Qué sucede si, en algún momento en el futuro, desea utilizar ese servicio comercial de un programa por lotes (que no tiene una sesión de usuario ...)? ¿O qué pasa si el mensaje de error no debe mostrarse al usuario actualmente conectado, sino a su supervisor (que puede preferir un idioma diferente)? ¿O qué pasaría si quisiera reutilizar la lógica empresarial en el cliente (que no tiene acceso a una base de datos ...)?
Claramente, la localización de mensajes depende de quién mira estos mensajes, es decir, es responsabilidad de la capa de presentación. Por lo tanto, lanzaría excepciones ordinarias del servicio comercial, que llevan un identificador de mensaje que luego se puede buscar en el controlador de excepciones de la capa de presentación en cualquier fuente de mensaje que utilice.
De esa manera, puede eliminar 3 dependencias innecesarias (UserSession, ExceptionFactory y probablemente descripciones), haciendo que su código sea más simple y versátil.
En términos generales, solo usaría fábricas estáticas para cosas a las que necesita acceso ubicuo, y que están garantizadas para estar disponibles en todos los entornos en los que podríamos querer ejecutar el código (como Logging). Para todo lo demás, usaría la inyección de dependencia simple y antigua.
fuente
Usar inyección de dependencia. Usar fábricas estáticas es un empleo del
Service Locator
antipatrón. Vea el trabajo seminal de Martin Fowler aquí: http://martinfowler.com/articles/injection.htmlSi sus argumentos de constructor se vuelven demasiado grandes y no está utilizando un contenedor DI, escriba sus propias fábricas para la creación de instancias, permitiendo que sea configurable, ya sea por XML o vinculando una implementación a una interfaz.
fuente
Yo también iría con la inyección de dependencia. Recuerde que DI no solo se realiza a través de constructores, sino también a través de establecedores de propiedades. Por ejemplo, el registrador podría inyectarse como una propiedad.
Además, es posible que desee utilizar un contenedor de IoC que pueda levantar parte de la carga para usted, por ejemplo, manteniendo los parámetros del constructor en las cosas que su lógica de dominio necesita en tiempo de ejecución (manteniendo el constructor de una manera que revele la intención de la clase y las dependencias del dominio real) y tal vez inyectar otras clases auxiliares a través de propiedades.
Un paso más allá que tal vez quiera ir es el Programa orientado a aspectos, que se implementa en muchos marcos principales. Esto puede permitirle interceptar (o "aconsejar" usar la terminología de AspectJ) al constructor de la clase e inyectar las propiedades relevantes, tal vez con un atributo especial.
fuente
No estoy del todo de acuerdo. Al menos no en general.
Fábrica simple:
Inyección simple:
Ambos fragmentos tienen el mismo propósito, establecen un enlace entre
IFoo
yFoo
. Todo lo demás es solo sintaxis.Cambiar
Foo
aADifferentFoo
requiere exactamente el mismo esfuerzo en cualquiera de los ejemplos de código.He escuchado a personas argumentar que la inyección de dependencia permite el uso de diferentes enlaces, pero se puede hacer el mismo argumento sobre la fabricación de diferentes fábricas. Elegir la encuadernación correcta es exactamente tan complejo como elegir la fábrica correcta.
Los métodos de fábrica le permiten, por ejemplo, usar
Foo
en algunos lugares yADifferentFoo
en otros lugares. Algunos pueden llamar a esto bueno (útil si lo necesita), algunos pueden llamar a esto malo (podría hacer un trabajo a medias para reemplazar todo).Sin embargo, no es tan difícil evitar esta ambigüedad si se adhiere a un único método que regresa
IFoo
para que siempre tenga una sola fuente. Si no quieres dispararte en el pie, no sostengas un arma cargada o asegúrate de no apuntar a tu pie.Esta es la razón por la cual algunas personas prefieren recuperar explícitamente dependencias en el constructor, así:
He escuchado argumentos pro (sin hincha del constructor), he escuchado argumentos con (el uso del constructor permite más automatización DI).
Personalmente, aunque me he rendido a nuestro superior que quiere usar argumentos de constructor, noté un problema con la lista desplegable en VS (arriba a la derecha, para examinar los métodos de la clase actual) donde los nombres de los métodos se pierden de vista cuando uno de las firmas de método es más largo que mi pantalla (=> el constructor hinchado).
A nivel técnico, no me importa de ninguna manera. Cualquiera de las opciones requiere tanto esfuerzo para escribir. Y dado que está utilizando DI, por lo general no llamará a un constructor manualmente. Pero el error de Visual Studio UI me hace favorecer no hinchar el argumento del constructor.
Como nota al margen, la inyección de dependencia y las fábricas no son mutuamente excluyentes . He tenido casos en los que, en lugar de insertar una dependencia, he insertado una fábrica que genera dependencias (afortunadamente, NInject te permite usar
Func<IFoo>
para que no necesites crear una clase de fábrica real).Los casos de uso para esto son raros pero existen.
fuente
En este ejemplo simulado, se usa una clase de fábrica en tiempo de ejecución para determinar qué tipo de objeto de solicitud HTTP entrante se debe instanciar, según el método de solicitud HTTP. La fábrica misma recibe una instancia de un contenedor de inyección de dependencia. Esto permite que la fábrica haga su determinación de tiempo de ejecución y permite que el contenedor de inyección de dependencias maneje las dependencias. Cada objeto de solicitud HTTP entrante tiene al menos cuatro dependencias (superglobales y otros objetos).
El código del cliente para una configuración de tipo MVC, dentro de un sistema centralizado
index.php
, podría tener el siguiente aspecto (se omiten las validaciones).Alternativamente, puede eliminar la naturaleza estática (y la palabra clave) de la fábrica y permitir que un inyector de dependencia administre todo (por lo tanto, el constructor comentado). Sin embargo, tendrá que cambiar algunas (no las constantes) de las referencias de miembros de clase (
self
) a miembros de instancia ($this
).fuente