lunes, 16 de marzo de 2020

Diseñando Inversion de control e injección de dependencias


Invertir el control consiste en cambiar el sentido de las "invocaciones"entre módulos. Es decir, si el módulo A llama o invoca al módulo B, hacer que B sea el que llame o invoque a A. En los lenguajes bajo el paradigma de orientación a objetos, esta inversión es posible gracias a los módulos de tipo interface o prototipo. Estos módulos permiten definir contratos de comportamiento, de forma que no contienen la implementación de dicho comportamiento. Solo contienen los argumentos necesarios para llevar a cabo el comportamiento y marcan el tipo de resultado que se va a obtener después de realizar ese comportamiento.

El siguiente ejemplo escrito en java muestra lo explicado

public interface Sport {
    void practice();
}

public class Basketball implements Sport {
    @Override
    public void practice() {
        doIt();
    }

El módulo Sport es una módulo de tipo interface. El módulo Basketball implementa esta interface y por ello está obligado a implementar el método practice() que no recibe argumentos y devuelve void como así prescribe la interfaz. Al implementar una interface es obligatorio implementar los métodos prescritos, usando el nombre, argumentos y tipo de retorno establecido.

¿Para qué es esto útil y como se relaciona con la inversión de control? Imaginemos que se requiere construir una app que gestiona partidos de diferentes deportes, su objetivo es llevar el calendario de eventos deportivos. El sistema debe tratar los eventos (Match), de los distintos deportes (Football y Basketball), por ejemplo se puede plantear el siguiente escenario:



El tipo Match crea y usa objetos de tipo Sport. Sport que es una interface, declara que todos los tipos que lo implementen tienen que tener un método practice(). En el ejemplo estos dos tipos obligados por la interfaz a implementar practice() son Football y Basketball.

En una primera instancia se podría haber pensado en el siguiente diseño:


Este diseño puede parecer más sencillo, pero establece un acoplamiento fuerte entre Match y los distintos deportes que se vayan a gestionar en la App. Es decir cada vez que se añada un deporte nuevo habrá que modificar Match. Además como no está regulada la implementación por ningún tipo de interfaz cada deporte se puede comportar de forma distinta, de manera que el tratamiento de cada deporte será diferente.

Para ilustrar esto veamos como queda la implementación de Match en el diseño con interfaz:

 public class Match {

    private final List sports;

    public Match(List sports) {
        this.sports = sports;
    }

    public void implement() {
    sports.forEach(s -> {
            s.practice();
        });
    }
}

Se observa que los deportes se tratan de forma uniforme independientemente de su tipo. Siempre se usa practice().

En este ejemplo queda por ver que tipo es responsable de crear los deportes. Y ese tipo es App, que tiene la siguiente implementación:

public class App {

     public static void main(String[] args) {

       Football football = new Football();
       Basketball basketball = new Basketball();
       List sports = new ArrayList();
       sports.add(football);
       sports.add(basketball);
       Match m = new Match(sports);
       m.implement();

    } 

}


Esta clase crea los deportes y se los injecta a Match a través de su constructor. Y aquí está el ejemplo del segundo concepto anunciado en el título del post "injección de dependencias". Esto quiere decir que las dependencias no las crea directamente Match, es decir no hace ni new Football(); ni new Basketball();. Los objetos de tipo Football y Basketball son creados por App y enviados como argumentos en una lista al constructor de Match. En el caso de necesitar incorporar nuevos deportes se creará su tipo y se implementara practice() como sea oportuno para ese deporte. Luego lo que habrá que modificar será App, Match quedará inmutable.

Este tipo de diseños está encaminado a que las clases de alto nivel, en este caso Match, no dependan de las de bajo nivel, en este caso Football y Basketball. Con lo que se han invertido las invocaciones. En este caso no s Match el que invoca a Football y Bascketball, es App quien invoca a Match y le inyecta los deportes a través del constructor.

Conclusión

Las dependencias son un asunto de la máxima importancia cuando se trata de sistemas informáticos compuestos por multitud de módulos. Estos colaboran entre ellos para llevar a cabo la misión para la que se han construido y para colaborar necesitan establecer dependencias. A todo esto en ingeniería del software se le llama acoplamiento y cuanto menor sea el acoplamiento entre módulos mejor es el sistema. Esto se debe a que sistemas con módulos poco acoplados permiten hacer cambios de forma más sencilla debido a que un cambio en un módulo no provoca que haya que cambiar los módulos que dependen de él y/o los módulos que dependen de él son los mínimos posible. En el caso de este ejemplo es menor el acoplamiento entre Match y Football y Backetball haciendo uso del interface Sport.

El código completo de este ejemplo se puede descargar en https://github.com/MarisaAfuera/inversionofcontrol

No hay comentarios:

Publicar un comentario