Digamos que tenemos 1001 clientes que construyen sus dependencias directamente en lugar de aceptar inyecciones. Refactorizar el 1001 no es una opción según nuestro jefe. En realidad, ni siquiera se nos permite acceder a su fuente, solo a los archivos de clase.
Lo que se supone que debemos hacer es "modernizar" el sistema por el que pasan estos 1001 clientes. Podemos refactorizar todo lo que queramos. Las dependencias son parte de ese sistema. Y algunas de esas dependencias que se supone que debemos cambiar para tener una nueva implementación.
Lo que nos gustaría hacer es tener la capacidad de configurar diferentes implementaciones de dependencias para satisfacer a esta multitud de clientes. Lamentablemente, DI no parece una opción ya que los clientes no aceptan inyecciones con constructores o setters.
Las opciones:
1) Refactorice la implementación del servicio que usan los clientes para que haga lo que los clientes necesitan ahora. Bang, hemos terminado. No es flexible No es complejo
2) Refactorice la implementación para que delegue su trabajo a otra dependencia que adquiera a través de una fábrica. Ahora podemos controlar qué implementación utilizan todos refactorizando la fábrica.
3) Refactorice la implementación para que delegue su trabajo a otra dependencia que adquiera a través de un localizador de servicios. Ahora podemos controlar qué implementación utilizan todos configurando el localizador de servicios que podría ser simplemente una hashmap
cadena de objetos con una pequeña conversión.
4) Algo en lo que ni siquiera he pensado todavía.
El objetivo:
Minimice el daño de diseño causado por arrastrar el antiguo código de cliente mal diseñado al futuro sin agregar una complejidad sin sentido.
Los clientes no deben conocer o controlar la implementación de sus dependencias, pero insisten en construirlas new
. No podemos controlar el, new
pero controlamos la clase que están construyendo.
Mi pregunta:
¿Qué he dejado de considerar?
¿realmente necesita una posibilidad de configurar entre diferentes implementaciones? ¿Con qué propósito?
Agilidad. Muchas incógnitas. La gerencia quiere potencial para el cambio. Solo pierde la dependencia del mundo exterior. También probando.
¿necesita una mecánica de tiempo de ejecución, o simplemente una mecánica de tiempo de compilación para cambiar entre diferentes implementaciones? ¿Por qué?
La mecánica del tiempo de compilación es bastante probable. Excepto por las pruebas.
¿Qué granularidad necesita para cambiar entre implementaciones? ¿De repente? ¿Por módulo (cada uno contiene un grupo de clases)? ¿Por clase?
De los 1001, solo uno se ejecuta a través del sistema en cualquier momento. Cambiar lo que todos los clientes usan a la vez probablemente esté bien. Sin embargo, el control individual de las dependencias es probablemente importante.
¿Quién necesita controlar el interruptor? ¿Solo tu / tu equipo de desarrolladores? ¿Un administrador? Cada cliente por su cuenta? ¿O los desarrolladores de mantenimiento para el código del cliente? Entonces, ¿qué tan fácil / robusta / infalible necesita ser la mecánica?
Desarrollador para pruebas. Administrador a medida que cambian las dependencias de hardware externo. Debe ser fácil de probar y configurar.
Nuestro objetivo es mostrar que el sistema puede rehacerse rápidamente y modernizarse.
caso de uso real para el interruptor de implementación?
Una es que el software proporcionará algunos datos hasta que la solución de hardware esté lista.
fuente
Respuestas:
Bueno, no estoy seguro de comprender completamente los detalles técnicos y sus diferencias exactas de sus soluciones compatibles, pero en mi humilde opinión, primero debe averiguar qué tipo de flexibilidad realmente necesita.
Las preguntas que debe hacerse son:
¿realmente necesita una posibilidad de configurar entre diferentes implementaciones? ¿Con qué propósito?
¿necesita una mecánica de tiempo de ejecución, o simplemente una mecánica de tiempo de compilación para cambiar entre diferentes implementaciones? ¿Por qué?
¿Qué granularidad necesita para cambiar entre implementaciones? ¿De repente? ¿Por módulo (cada uno contiene un grupo de clases)? ¿Por clase?
¿Quién necesita controlar el interruptor? ¿Solo tu / tu equipo de desarrolladores? ¿Un administrador? Cada cliente por su cuenta? ¿O los desarrolladores de mantenimiento para el código del cliente? Entonces, ¿qué tan fácil / robusta / infalible necesita ser la mecánica?
Teniendo en mente las respuestas a estas preguntas, elija la solución más simple que se le ocurra y que le brinde la flexibilidad requerida. No implemente ninguna flexibilidad de la que no esté seguro "por si acaso" si significa un esfuerzo adicional.
Como respuesta a sus respuestas: si tiene al menos un caso de uso real a la mano, úselo para validar sus decisiones de diseño. Use ese caso de uso para descubrir qué tipo de solución funciona mejor para usted. Simplemente pruebe si una "fábrica" o un "localizador de servicios" le proporciona lo que necesita, o si necesita algo más. Si crees que ambas soluciones son igualmente buenas para tu caso, tira un dado.
fuente
Solo para asegurarme de que estoy haciendo esto bien. Tienes algún servicio hecho de algunas clases, por ejemplo,
C1,...,Cn
y un grupo de clientes que llaman directamentenew Ci(...)
. Así que creo que estoy de acuerdo con la estructura general de su solución, que es crear un nuevo servicio interno con algunas clases nuevasD1,...,Dn
que sean agradables y modernas e inyectar sus dependencias (permitiendo tales dependencias a través de la pila) y luego reescribirCi
para hacer nada más que instanciar e delgate aDi
. La pregunta es cómo hacerlo y usted sugirió un par de formas2
y3
.Para dar una sugerencia similar a
2
. Puede usar la inyección de dependencia en todoDi
y de forma interna y luego crear una raíz de composición normalR
(usando un marco si lo considera apropiado) que es responsable de ensamblar el gráfico de objetos. EmpújeseR
detrás de una fábrica estática y deje que cada unoCi
loDi
supere. Entonces, por ejemplo, podrías tenerEsencialmente, esta es su solución 2, pero reúne todas sus fábricas en una ubicación junto con el resto de la inyección de dependencia.
fuente
getInstance()
?Comience con implementaciones simples, nada sofisticadas y singulares.
Si luego necesita crear implementaciones adicionales, es una cuestión de cuándo se produce la decisión de implementación.
Si necesita flexibilidad en el "tiempo de instalación" (la instalación de cada cliente utiliza una única implementación estática), aún no necesita hacer nada sofisticado. Simplemente ofrece diferentes DLL o SO (o cualquiera que sea su formato lib). El cliente solo necesita poner el correcto en la
lib
carpeta ...Si necesita flexibilidad en el tiempo de ejecución, solo necesitará un adaptador delgado y un mecanismo de selección de implementación. Ya sea que use una fábrica, un localizador o un contenedor de IoC, es principalmente discutible. Lo único diferencias significativas entre un adaptador y un localizador son A) Naming y B) Si el objeto devuelto es un singleton o una instancia dedicada. Y la gran diferencia entre un contenedor IoC y una fábrica / localizador es quién llama a quién . (A menudo es una cuestión de preferencia personal).
fuente