domingo, 20 de mayo de 2007

Inversion of Control - Dependency Injection

Para empezar, clarifiquemos algo: Inversion of Control no es lo mismo que Dependency Injection. Inversion of control estamos haciendo en montañas de lugares, en Visual Basic cuando las aplicaciones dejaron de controlar la UI y permitieron a un tercero hacerlo por ellas y decirle que me "avise" (en ese ejemplo mediante eventos) cuando tengo que hacer algo estabamos "invirtiendo el control" y delegando el manejo de la UI.

Dependency Injection

Veamos un ejemplo para entender de que estamos hablando.

Estamos escribiendo un componente que nos devuelve la lista de películas dirigidas por un director en particular.

La implementación del método es extremadamente ingenua pero nos sirve para nuestro fin.


class MovieLister...

private MovieFinder finder;

public MovieLister() {
finder = new ColonDelimitedMovieFinder("movies1.txt");
}

public Movie[] moviesDirectedBy(String arg) {
List allMovies = finder.findAll();
for (Iterator it = allMovies.iterator(); it.hasNext();) {
Movie movie = (Movie) it.next();
if (!movie.getDirector().equals(arg)) it.remove();
}
return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);
}
Lo importante de este método es el finder ya que nuestro algoritmo es independiente de como se persisten las películas.

En este caso, en el constructor de MovieLister estamos instanciado una implementación de MovieFinder que podría levantar un archivo delimitado.

La interface MovieFinder es bien simple:


public interface MovieFinder {
List findAll();
}
Lo que tenemos hasta acá lo podemos ver representado en el siguiente diagrama, MovieLister conoce tanto a la interface MovieFinder como a la implementación MovieFinderImpl.




La IoC en este caso está dada por "tercerizar" la obtención de la implementación real de la interface. De esta manera el componente MovieLister se independiza de las posibles implementaciones del MovieFinder. Esto lo vemos en el siguiente diagrama

La Dependency Injection puede hacerse de tres maneras:

  1. Constructor Injection: En este caso la clase MovieLister tiene un constructor que recibe un MovieFinder.
  2. Setter Injection: Aquí la clase MovieLister posee un método setFinder(MovieFinder finder). Este es el caso de Spring.
    Aquí viene la mejor parte, como hacemos para que todo esto funcione al unisono:

    En el archivo de configuración de spring definimos lo siguiente (también estamos haciendo que ColonMovieFinder reciba mediante spring el nombre del archivo de donde tomar los datos):

    <beans>
    <bean class="spring.MovieLister" id="MovieLister">
    <property name="finder">
    <ref local="MovieFinder">
    </property>
    </bean>
    <bean class="spring.ColonMovieFinder" id="MovieFinder">
    <property name="filename">
    <value>movies1.txt</value>
    </property>
    </bean>
    </beans>
  3. y un ejemplo de uso sería:
    public void testWithSpring() throws Exception {
    ApplicationContext ctx = new FileSystemXmlApplicationContext("spring.xml");
    MovieLister lister = (MovieLister) ctx.getBean("MovieLister");
    Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
    assertEquals("Once Upon a Time in the West", movies[0].getTitle());
    }
  4. Interface Injection: este caso lo veremos en otro momento ya que no es muy utilizado.

En resumen, lo principal es que se agrega un nuevo administrador, en el cual se definen para cada interface, una implementación y en tiempo de ejecución se le solicitan a él los beans.

No hay comentarios.: