Pregunta

Si entiendo correctamente, el mecanismo típico para la inyección de dependencia es inyectar a través del constructor de una clase o de una propiedad pública (miembro) de la clase.

Esto expone la dependencia que se está inyectando y viola el principio de encapsulación OOP.

¿Estoy en lo correcto al identificar esta compensación? ¿Cómo lidias con este problema?

Consulte también mi respuesta a mi propia pregunta a continuación.

¿Fue útil?

Solución

Hay otra forma de ver este problema que podría encontrar interesante.

Cuando usamos IoC / inyección de dependencia, no estamos usando conceptos OOP. Es cierto que estamos usando un lenguaje OO como "host", pero las ideas detrás de IoC provienen de la ingeniería de software orientada a componentes, no de OO.

El software de componentes tiene que ver con la gestión de dependencias, un ejemplo de uso común es el mecanismo de ensamblaje de .NET. Cada ensamblaje publica la lista de ensamblajes a los que hace referencia, y esto hace que sea mucho más fácil reunir (y validar) las piezas necesarias para una aplicación en ejecución.

Al aplicar técnicas similares en nuestros programas OO a través de IoC, nuestro objetivo es hacer que los programas sean más fáciles de configurar y mantener. La publicación de dependencias (como parámetros del constructor o lo que sea) es una parte clave de esto. La encapsulación no se aplica realmente, como en el mundo orientado a componentes / servicios, no hay un 'tipo de implementación' para que se filtren los detalles.

Lamentablemente, nuestros idiomas actualmente no segregan los conceptos orientados a objetos de grano fino de los orientados a componentes de grano más grueso, por lo que esta es una distinción que debe tener en mente solamente :)

Otros consejos

Es una buena pregunta, pero en algún momento, la encapsulación en su forma más pura necesita para ser violada si el objeto debe cumplir su dependencia. Algunos proveedores de la dependencia deben saber que el objeto en cuestión requiere un Foo , y el proveedor debe tener una manera de proporcionar el Foo al objeto.

Clásicamente, este último caso se maneja como usted dice, a través de argumentos de constructor o métodos de establecimiento. Sin embargo, esto no es necesariamente cierto. Sé que las últimas versiones del framework Spring DI en Java, por ejemplo, le permiten anotar campos privados (por ejemplo, con @Autowired ) y la dependencia se establecerá a través de Reflexión sin necesidad de exponer la dependencia a través de cualquiera de las clases de métodos públicos / constructores. Este podría ser el tipo de solución que estaba buscando.

Dicho esto, tampoco creo que la inyección del constructor sea un problema. Siempre sentí que los objetos deberían ser completamente válidos después de la construcción, de modo que cualquier cosa que necesiten para desempeñar su función (es decir, estar en un estado válido) debe proporcionarse a través del constructor de todos modos. Si tiene un objeto que requiere un colaborador para trabajar, me parece bien que el constructor anuncie públicamente este requisito y se asegure de que se cumple cuando se crea una nueva instancia de la clase.

Lo ideal es que cuando trabaje con objetos, interactúe con ellos a través de una interfaz, y mientras más haga esto (y tenga dependencias conectadas a través de DI), menos tendrá que tratar con los constructores. En la situación ideal, su código no trata ni crea instancias concretas de clases; por lo que solo se le otorga un IFoo a través de DI, sin preocuparse por lo que el constructor de FooImpl indica que necesita hacer su trabajo, y de hecho sin ni siquiera ser consciente de FooImpl . Desde este punto de vista, la encapsulación es perfecta.

Esta es una opinión, por supuesto, pero en mi opinión, DI no viola necesariamente la encapsulación y, de hecho, puede ayudarlo al centralizar todo el conocimiento necesario de los elementos internos en un solo lugar. Esto no solo es algo bueno en sí mismo, sino que, incluso mejor, este lugar está fuera de su propio código base, por lo que ninguno de los códigos que escriba debe conocer las dependencias de las clases.

  

Esto expone la dependencia que se está inyectando y viola el principio de encapsulación OOP.

Hablando francamente, todo viola la encapsulación. :) Es una especie de principio tierno que debe ser tratado bien.

Entonces, ¿qué viola la encapsulación?

Herencia hace .

  

" Debido a que la herencia expone una subclase a los detalles de la implementación de su padre, a menudo se dice que 'la herencia rompe la encapsulación' " ;. (Gang of Four 1995: 19)

Programación orientada a aspectos hace . Por ejemplo, registras la devolución de llamada enMethodCall () y eso te da una gran oportunidad para insertar código en la evaluación del método normal, agregar efectos secundarios extraños, etc.

La declaración de amigo en C ++ hace .

La extensión de clase en Ruby hace . Solo redefine un método de cadena en algún lugar después de que una clase de cadena esté completamente definida.

Bueno, muchas cosas que hace .

La encapsulación es un principio bueno e importante. Pero no el único.

switch (principle)
{
      case encapsulation:
           if (there_is_a_reason)
      break!
}

Sí, DI viola la encapsulación (también conocida como " ocultación de información ").

Pero el verdadero problema se presenta cuando los desarrolladores lo usan como una excusa para violar los principios KISS (Keep It Short and Simple) y YAGNI (No lo vas a necesitar).

Personalmente, prefiero soluciones simples y efectivas. Principalmente utilizo el " nuevo " operador para instanciar dependencias con estado siempre y cuando sea necesario. Es simple, bien encapsulado, fácil de entender y fácil de probar. Entonces, ¿por qué no?

Un buen contenedor / sistema de inyección de depenancy permitirá la inyección del constructor. Los objetos dependientes serán encapsulados, y no necesitan ser expuestos públicamente en absoluto. Además, al usar un sistema de DP, ninguno de su código, incluso " sabe " los detalles de cómo se construye el objeto, posiblemente incluso incluyendo el objeto que se está construyendo. Hay más encapsulación en este caso ya que casi todo su código no solo está protegido del conocimiento de los objetos encapsulados, sino que ni siquiera participa en la construcción de los objetos.

Ahora, asumo que está comparando con el caso en el que el objeto creado crea sus propios objetos encapsulados, probablemente en su constructor. Mi comprensión de DP es que queremos quitar esta responsabilidad del objeto y dársela a otra persona. Con ese fin, el " otra persona " ;, que es el contenedor DP en este caso, tiene un conocimiento íntimo que " viola " encapsulamiento el beneficio es que saca ese conocimiento del objeto, él mismo. Alguien tiene que tenerlo. El resto de su aplicación no.

Lo pensaría de esta manera: el sistema / contenedor de inyección de dependencia viola la encapsulación, pero su código no. De hecho, su código es más " encapsulado " entonces nunca.

No viola la encapsulación. Usted está proporcionando un colaborador, pero la clase decide cómo se usa. Siempre y cuando siga Diga que no pregunte las cosas están bien. Creo que es preferible la inyección de constructores, pero los instaladores pueden estar bien, siempre y cuando sean inteligentes. Es decir, contienen lógica para mantener los invariantes que representa la clase.

Esto es similar a la respuesta con votos más altos, pero quiero pensar en voz alta, quizás otros también vean las cosas de esta manera.

  • El OO clásico usa constructores para definir la inicialización " pública " contrato para los consumidores de la clase (ocultando TODOS los detalles de la implementación; también conocido como encapsulación). Este contrato puede garantizar que, después de la creación de instancias, tenga un objeto listo para usar (es decir, que el usuario no tenga que seguir los pasos de inicialización adicionales).

  • (constructor) DI innegablemente rompe la encapsulación sangrando detalles de implementación a través de esta interfaz de constructor público. Mientras aún consideremos al constructor público responsable de definir el contrato de inicialización para los usuarios, hemos creado una violación horrible de la encapsulación.

Ejemplo teórico:

La clase Foo tiene 4 métodos y necesita un entero para la inicialización, por lo que su constructor se parece a Foo (tamaño int) y queda inmediatamente claro para los usuarios de la clase Foo que deben proporcionar un tamaño en la instanciación para que Foo funcione.

Diga que esta implementación particular de Foo también puede necesitar un IWidget para hacer su trabajo. La inyección del constructor de esta dependencia nos haría crear un constructor como Foo (tamaño int, widget IWidget)

Lo que me molesta de esto es que ahora tenemos un constructor que está combinando datos de inicialización con dependencias: una entrada es de interés para el usuario de la clase ( tamaño ), el otro es una dependencia interna que solo sirve para confundir al usuario y es un detalle de implementación ( widget ).

El parámetro de tamaño NO es una dependencia, es un simple valor de inicialización por instancia. IoC es excelente para las dependencias externas (como un widget) pero no para la inicialización interna del estado.

Peor aún, ¿qué pasa si el Widget solo es necesario para 2 de los 4 métodos en esta clase; ¡Puede que esté incurriendo en una sobrecarga de creación de instancias para Widget, aunque no se pueda usar!

¿Cómo comprometer / reconciliar esto?

Un enfoque es cambiar exclusivamente a interfaces para definir el contrato de operación; y abolir el uso de constructores por parte de los usuarios. Para ser coherentes, se debería acceder a todos los objetos solo a través de interfaces y crear una instancia solo a través de algún tipo de resolución (como un contenedor IOC / DI). Sólo el contenedor llega a instanciar las cosas.

Se encarga de la dependencia del Widget, pero ¿cómo se inicializa " tamaño " ¿Sin recurrir a un método de inicialización separado en la interfaz de Foo? Al utilizar esta solución, perdimos la capacidad de garantizar que una instancia de Foo esté completamente inicializada en el momento en que la reciba. Bummer, porque realmente me gusta la idea y la simplicidad de la inyección del constructor.

¿Cómo puedo lograr una inicialización garantizada en este mundo DI, cuando la inicialización es MÁS que SOLO dependencias externas?

Como señaló Jeff Sternal en un comentario a la pregunta, la respuesta depende completamente de cómo se define encapsulación .

Parece que hay dos campos principales de lo que significa la encapsulación:

  1. Todo lo relacionado con el objeto es un método en un objeto. Por lo tanto, un objeto File puede tener métodos para Save , Print , Display , ModifyText , etc.
  2. Un objeto es su propio pequeño mundo, y no depende de un comportamiento externo.

Estas dos definiciones están en directa contradicción entre sí. Si un objeto File puede imprimirse, dependerá en gran medida del comportamiento de la impresora. Por otro lado, si simplemente sabe sobre algo que puede imprimir (un IFilePrinter o alguna interfaz de este tipo), entonces el objeto File no tiene que saber nada sobre la impresión, por lo que trabajar con ella traerá menos dependencias al objeto.

Por lo tanto, la inyección de dependencia romperá la encapsulación si utiliza la primera definición. Pero, francamente, no sé si me gusta la primera definición, claramente no se escala (si es así, MS Word sería una gran clase).

Por otro lado, la inyección de dependencia es casi obligatoria si está utilizando la segunda definición de encapsulación.

La encapsulación pura es un ideal que nunca se puede lograr. Si todas las dependencias estuvieran ocultas, entonces no tendría la necesidad de DI en absoluto. Piénselo de esta manera, si realmente tiene valores privados que pueden internalizarse dentro del objeto, por ejemplo, el valor entero de la velocidad de un objeto de automóvil, entonces no tiene una dependencia externa y no necesita invertir o inyectar esa dependencia. Este tipo de valores de estado interno que se operan exclusivamente mediante funciones privadas es lo que siempre desea encapsular.

Pero si está construyendo un automóvil que desea un determinado tipo de objeto de motor, entonces tiene una dependencia externa. Puede crear una instancia de ese motor, por ejemplo el nuevo GMOverHeadCamEngine (), internamente dentro del constructor del objeto del automóvil, preservando la encapsulación pero creando un acoplamiento mucho más insidioso a una clase concreta GMOverHeadCamEngine, o puede inyectar, permitiendo que su objeto del Carro funcione de forma agnóstica (y mucho más robusta) en, por ejemplo, una interfaz IEngine sin la dependencia concreta. El hecho de que use un contenedor de COI o un DI simple para lograrlo no es el punto, el punto es que tiene un Coche que puede usar muchos tipos de motores sin estar acoplado a ninguno de ellos, lo que hace que su base de código sea más flexible y eficiente. Menos propensos a los efectos secundarios.

DI no es una violación de la encapsulación, es una forma de minimizar el acoplamiento cuando la encapsulación se rompe necesariamente en la práctica dentro de prácticamente todos los proyectos OOP. La inyección de una dependencia en una interfaz minimiza externamente los efectos secundarios de acoplamiento y permite que sus clases se mantengan ajenas a la implementación.

Creo en la simplicidad. La aplicación de IOC / Dependecy Injection en las clases de dominio no hace ninguna mejora, excepto que hace que el código sea mucho más difícil de mantener al tener un archivo XML externo que describe la relación. Muchas tecnologías como EJB 1.0 / 2.0 & amp; los puntales 1.1 están retrocediendo al reducir las cosas que se colocan en XML y tratar de ponerlas en código como annoation, etc. Por lo tanto, aplicar IOC a todas las clases que desarrolles hará que el código no tenga sentido.

IOC tiene beneficios cuando el objeto dependiente no está listo para su creación en tiempo de compilación. Esto puede ocurrir en la mayoría de los componentes de la arquitectura de nivel abstracto de infrastura, intentando establecer un marco de base común que puede necesitar trabajar para diferentes escenarios. En esos lugares el uso del COI tiene más sentido. Aún así, esto no hace que el código sea más simple / mantenible.

Como todas las otras tecnologías, esto también tiene PROs & amp; Contras. Mi preocupación es que implementamos las últimas tecnologías en todos los lugares, independientemente de su mejor uso del contexto.

Depende de si la dependencia es realmente un detalle de implementación o algo que el cliente querría o necesita saber de alguna manera u otra. Una cosa que es relevante es a qué nivel de abstracción se dirige la clase. Aquí hay algunos ejemplos:

Si tiene un método que utiliza el almacenamiento en caché bajo el capó para acelerar las llamadas, entonces el objeto de caché debe ser un Singleton o algo así y no se debe inyectar . El hecho de que la memoria caché se esté utilizando es un detalle de implementación que los clientes de su clase no deberían tener que preocuparse.

Si su clase necesita generar flujos de datos, es probable que tenga sentido inyectar el flujo de salida para que la clase pueda enviar fácilmente los resultados a una matriz, un archivo o cualquier otro lugar donde otra persona quiera enviar los datos.

Para un área gris, digamos que tienes una clase que hace simulación de Monte Carlo. Necesita una fuente de aleatoriedad. Por un lado, el hecho de que necesite esto es un detalle de la implementación, ya que al cliente realmente no le importa exactamente de dónde proviene la aleatoriedad. Por otro lado, dado que los generadores de números aleatorios del mundo real hacen concesiones entre el grado de aleatoriedad, la velocidad, etc., que el cliente puede querer controlar, y el cliente puede querer controlar la siembra para obtener un comportamiento repetible, la inyección puede tener sentido. En este caso, sugeriría ofrecer una forma de crear la clase sin especificar un generador de números aleatorios, y utilizar un Singleton de subproceso local como predeterminado. Si / cuando surja la necesidad de un control más preciso, proporcione otro constructor que permita inyectar una fuente de aleatoriedad.

La encapsulación solo se rompe si una clase tiene la responsabilidad de crear el objeto (que requiere conocimiento de los detalles de la implementación) y luego usa la clase (que no requiere el conocimiento de estos detalles). Explicaré por qué, pero primero un análisis rápido de coches:

  

Cuando conducía mi viejo Kombi de 1971,   Podría presionar el acelerador y se   fue (un poco) más rápido. No lo hice   Necesito saber por qué, pero los chicos que   construyó el Kombi en la fábrica sabía   exactamente por qué.

Pero volvamos a la codificación. Encapsulación está " ocultando un detalle de implementación de algo que usa esa implementación. " La encapsulación es algo bueno porque los detalles de la implementación pueden cambiar sin que el usuario de la clase lo sepa.

Cuando se usa la inyección de dependencia, la inyección de constructor se usa para construir objetos de tipo servicio (a diferencia de los objetos entidad / valor que modelan el estado). Cualquier variable miembro en el objeto de tipo de servicio representa detalles de implementación que no deben filtrarse. p.ej. número de puerto de socket, credenciales de la base de datos, otra clase a la que llamar para realizar el cifrado, un caché, etc.

El constructor es relevante cuando la clase se crea inicialmente. Esto sucede durante la fase de construcción mientras su contenedor DI (o fábrica) conecta todos los objetos de servicio. El contenedor DI solo conoce los detalles de la implementación. Sabe todo sobre detalles de implementación, como lo saben los tipos de bujías en la fábrica de Kombi.

En el tiempo de ejecución, el objeto de servicio que se creó se llama apon para hacer un trabajo real. En este momento, la persona que llama al objeto no sabe nada de los detalles de la implementación.

  

Ese soy yo conduciendo mi Kombi a la playa.

Ahora, de vuelta a la encapsulación. Si los detalles de la implementación cambian, entonces la clase que usa esa implementación en tiempo de ejecución no necesita cambiar. La encapsulación no está rota.

  

También puedo conducir mi auto nuevo a la playa. La encapsulación no está rota.

Si los detalles de la implementación cambian, el contenedor DI (o la fábrica) debe cambiar. En primer lugar, nunca intentaste ocultar los detalles de la implementación de la fábrica.

Habiendo luchado un poco más con el problema, ahora estoy en la opinión de que la Inyección de Dependencia (en este momento) viola la encapsulación hasta cierto punto. Sin embargo, no me malinterpretes, creo que usar la inyección de dependencia bien vale la pena en la mayoría de los casos.

El caso por el que DI viola la encapsulación se aclara cuando el componente en el que está trabajando se entregará a un " externo " fiesta (piensa en escribir una biblioteca para un cliente).

Cuando mi componente requiere que los subcomponentes se inyecten a través del constructor (o propiedades públicas) no hay garantía para

  

" evita que los usuarios configuren los datos internos del componente en un estado no válido o inconsistente "

Al mismo tiempo, no se puede decir que

  

" los usuarios del componente (otras piezas de software) solo necesitan saber qué hace el componente, y no pueden depender de los detalles de cómo lo hace " .

Ambas citas son de wikipedia .

Para dar un ejemplo específico: necesito entregar una DLL del lado del cliente que simplifique y oculte la comunicación a un servicio WCF (esencialmente una fachada remota). Debido a que depende de 3 clases de proxy WCF diferentes, si tomo el enfoque DI, me veo obligado a exponerlas a través del constructor. Con eso expongo los elementos internos de mi capa de comunicación que trato de ocultar.

En general estoy a favor de DI. En este ejemplo particular (extremo), me parece peligroso.

También luché con esta idea. Al principio, el "requisito" de usar el contenedor DI (como Spring) para crear una instancia de un objeto se siente como saltar a través de aros. Pero en realidad, realmente no es un aro, es solo otra forma 'publicada' de crear objetos que necesito. Claro, la encapsulación está 'rota' porque alguien 'fuera de clase' sabe lo que necesita, pero realmente no es el resto del sistema el que sabe eso, es el contenedor DI. Nada mágico sucede de manera diferente porque DI 'sabe' que un objeto necesita otro.

De hecho, se pone aún mejor: al concentrarme en Fábricas y Repositorios, ¡ni siquiera tengo que saber que DI está involucrada en absoluto! Eso me vuelve a poner la tapa a la encapsulación. Whew!

PS. Al proporcionar Inyección de dependencia , no necesariamente rompe Encapsulación . Ejemplo:

obj.inject_dependency(  factory.get_instance_of_unknown_class(x)  );

El código del cliente todavía no conoce los detalles de la implementación.

Tal vez esta es una forma ingenua de pensar en ello, pero ¿cuál es la diferencia entre un constructor que toma un parámetro entero y un constructor que toma un servicio como parámetro? ¿Significa esto que definir un número entero fuera del nuevo objeto y alimentarlo en el objeto rompe la encapsulación? Si el servicio solo se usa dentro del nuevo objeto, no veo cómo se rompería la encapsulación.

Además, al utilizar algún tipo de función de conexión automática (Autofac para C #, por ejemplo), hace que el código sea extremadamente limpio. Al crear métodos de extensión para el constructor Autofac, pude eliminar MUCHO código de configuración DI que habría tenido que mantener con el tiempo a medida que crecía la lista de dependencias.

Creo que es evidente que al menos el DI debilita significativamente la encapsulación. Además de esto, aquí hay otras desventajas de DI a considerar.

  1. Hace que el código sea más difícil de reutilizar. Un módulo que un cliente puede usar sin tener que proporcionar explícitamente dependencias, obviamente es más fácil de usar que uno donde el cliente tiene que descubrir de alguna manera cuáles son las dependencias de ese componente y luego ponerlas a disposición de alguna manera. Por ejemplo, un componente creado originalmente para ser utilizado en una aplicación ASP puede esperar que sus dependencias sean proporcionadas por un contenedor DI que proporciona instancias de objetos con tiempos de vida relacionados con las solicitudes http del cliente. Es posible que esto no sea fácil de reproducir en otro cliente que no venga con el mismo contenedor DI incorporado que la aplicación ASP original.

  2. Puede hacer que el código sea más frágil. Las dependencias proporcionadas por la especificación de la interfaz se pueden implementar de formas inesperadas, lo que da lugar a toda una clase de errores de tiempo de ejecución que no son posibles con una dependencia concreta resuelta estáticamente.

  3. Puede hacer que el código sea menos flexible en el sentido de que puede terminar con menos opciones sobre cómo desea que funcione. No todas las clases necesitan tener todas sus dependencias durante toda la vida útil de la instancia propietaria, sin embargo, con muchas implementaciones de DI no tiene otra opción.

Con eso en mente, creo que la pregunta más importante se convierte en "" ¿una dependencia particular necesita ser especificada externamente? " ;. En la práctica, rara vez he encontrado que sea necesario hacer una dependencia suministrada externamente solo para soportar las pruebas.

Cuando una dependencia realmente necesita ser suministrada externamente, eso normalmente sugiere que la relación entre los objetos es una colaboración en lugar de una dependencia interna, en cuyo caso el objetivo apropiado es la encapsulación de cada clase , en lugar de la encapsulación de una clase dentro de la otra.

En mi experiencia, el principal problema relacionado con el uso de DI es si comienza con un marco de aplicación con DI integrado o si agrega soporte DI a su base de código, por alguna razón, la gente asume que, dado que cuenta con soporte DI, debe Sé la forma correcta de instanciar todo . Simplemente, ni siquiera se molestan en formular la pregunta "¿esta dependencia debe especificarse externamente?". Y lo que es peor, también comienzan a intentar forzar a todos los demás a usar el soporte DI para todo también.

El resultado de esto es que, de manera inexorable, su base de código comienza a convertirse en un estado en el que crear cualquier instancia de cualquier cosa en su base de código requiere resmas de configuración de contenedor de DI obtusa, y depurar cualquier cosa es el doble de difícil porque tiene la carga de trabajo adicional de intentarlo. para identificar cómo y dónde se creó una instancia de cualquier cosa.

Así que mi respuesta a la pregunta es la siguiente. Use DI donde pueda identificar un problema real que resuelva por usted, que no puede resolver de una manera más sencilla.

DI viola la encapsulación para objetos no compartidos - período. Los objetos compartidos tienen una vida útil fuera del objeto que se está creando y, por lo tanto, deben AGREGARSE en el objeto que se está creando. Los objetos que son privados para el objeto que se está creando deben COMPOSARSE en el objeto creado: cuando el objeto creado se destruye, lleva consigo el objeto compuesto. Tomemos el cuerpo humano como ejemplo. Lo que está compuesto y lo que está agregado. Si tuviéramos que usar DI, el constructor del cuerpo humano tendría cientos de objetos. Muchos de los órganos, por ejemplo, son (potencialmente) reemplazables. Pero, todavía se componen en el cuerpo. Las células sanguíneas se crean en el cuerpo (y se destruyen) todos los días, sin la necesidad de influencias externas (que no sean proteínas). Por lo tanto, las células sanguíneas son creadas internamente por el cuerpo: nueva BloodCell ().

Los defensores de DI argumentan que un objeto NUNCA debe usar el nuevo operador. Ese " purista " El enfoque no solo viola la encapsulación, sino también el principio de sustitución de Liskov para quien crea el objeto.

Estoy de acuerdo en que, llevado al extremo, la DI puede violar la encapsulación. Por lo general, DI expone dependencias que nunca fueron verdaderamente encapsuladas. Aquí hay un ejemplo simplificado tomado de Mi & # 353; ko Hevery's Singletons son los mentirosos patológicos :

Usted comienza con una prueba de CreditCard y escribe una prueba de unidad simple.

@Test
public void creditCard_Charge()
{
    CreditCard c = new CreditCard("1234 5678 9012 3456", 5, 2008);
    c.charge(100);
}

El próximo mes recibirá una factura por $ 100. ¿Por qué te cobraron? La prueba unitaria afectó a una base de datos de producción. Internamente, CreditCard llama a Database.getInstance () . La tarjeta de crédito de refactorización para que tome un DatabaseInterface en su constructor expone el hecho de que hay dependencia. Pero diría que, para empezar, nunca se encapsuló la dependencia ya que la clase CreditCard causa efectos secundarios visibles externamente. Si desea probar la tarjeta de crédito sin refactorizar, puede observar la dependencia.

@Before
public void setUp()
{
    Database.setInstance(new MockDatabase());
}

@After
public void tearDown()
{
    Database.resetInstance();
}

No creo que valga la pena preocuparse si la exposición de la base de datos como una dependencia reduce la encapsulación, porque es un buen diseño. No todas las decisiones de DI serán tan directas. Sin embargo, ninguna de las otras respuestas muestra un ejemplo de contador.

Creo que es una cuestión de alcance. Cuando defina la encapsulación (sin saber cómo hacerlo), debe definir cuál es la funcionalidad encapsulada.

  1. Clase tal como está : lo que está encapsulando es la única responsabilidad de la clase. Lo que sabe hacer. Por ejemplo, ordenando. Si inyectas un comparador para ordenar, digamos, clientes, eso no es parte de lo encapsulado: quicksort.

  2. Funcionalidad configurada : si desea proporcionar una funcionalidad lista para usar, no está proporcionando la clase QuickSort, sino una instancia de la clase QuickSort configurada con un Comparator. En ese caso, el código responsable de crear y configurar debe estar oculto del código de usuario. Y esa es la encapsulación.

Cuando estás programando clases, es, implementando responsabilidades individuales en las clases, estás usando la opción 1.

Cuando estás programando aplicaciones, es que, al hacer que algo funcione correctamente concreto , estás usando repetidamente la opción 2.

Esta es la implementación de la instancia configurada:

<bean id="clientSorter" class="QuickSort">
   <property name="comparator">
      <bean class="ClientComparator"/>
   </property>
</bean>

Así es como lo usa otro código de cliente:

<bean id="clientService" class"...">
   <property name="sorter" ref="clientSorter"/>
</bean>

Está encapsulado porque si cambia la implementación (cambia la definición de bean clientSorter ) no interrumpe el uso del cliente. Tal vez, a medida que usa archivos XML con todos los escritos juntos, está viendo todos los detalles. Pero créeme, el código del cliente ( ClientService ) no sé nada sobre su clasificador.

Probablemente vale la pena mencionar que Encapsulation es algo dependiente de la perspectiva.

public class A { 
    private B b;

    public A() {
        this.b = new B();
    }
}


public class A { 
    private B b;

    public A(B b) {
        this.b = b;
    }
}

Desde la perspectiva de alguien que trabaja en la clase A , en el segundo ejemplo A sabe mucho menos sobre la naturaleza de this.b

Mientras que sin DI

new A()

vs

new A(new B())

La persona que mira este código sabe más sobre la naturaleza de A en el segundo ejemplo.

Con DI, al menos todo el conocimiento filtrado se encuentra en un solo lugar.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top