---
# System prepended metadata

title: 'Components d''accés a dades: Inversió de control (IoC) i injecció de dependències (DI)'
tags: [UF4 Components d'accés a dades]

---

# Components d'accés a dades: Inversió de control (IoC) i injecció de dependències (DI)

## Concepte de component de programari. Característiques

Un component és aquella part de la solució (de hardware o de software) que queda detallada:
*  pel que fa ==> **comportament del component** 
*  per com es comunica i interacciona amb l’exterior ==> **interfície del component**. 

En particular, tot component de programari (software) té les següents ***característiques***:

* **Encapsulament**, ja que un component sols es comunica per la seva interfície.
* **Independència** d’altres components per fer el que fa i de l’ordinador (HW) o sistema operatiu (SW) en el que funciona.
* **Re-usabilitat**, traslladar el component i reutilitzar-lo en altres solucions/aplicacions.

Com exemples de components de programari que accedeixen a dades tenim els data access objects (DAO),  dels quals ja hem parlat a l'hora d'introduir el patró DAO i que a més ja hem treballat en diferents activitats.

## El principi d'inversió de control

Fixeu-vos en el següent codi:

```
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Caracters {

    public static void main(String[] args) {
        
        System.out.print("Quin es el teu nom?: ");

        try (BufferedReader br1 = new BufferedReader(new InputStreamReader(System.in))) {
            String name = br1.readLine();
            System.out.println("El teu nom en majúscules és: " +  NameToMayus(name));
            System.out.print("Ara digues un caràcter a cercar dins el nom: ");
            char c1 = br1.readLine().toLowerCase().charAt(0);
            System.out.println("El teu nom té " + NumberChars(name,c1) + 
            " caràcters " + c1);
        } catch (Exception ioe) {
            System.err.println("Error d'I/O");
            ioe.printStackTrace();
        }
    }

    public static String NameToMayus(String name) {
		String nameM = name.toUpperCase();
		return nameM;
	}

    public static long NumberChars(String name, char c1) {
		long numberChars = 0;
		numberChars = name.toLowerCase().chars().filter(ch -> ch == c1).count();
		return numberChars;
	}
}
```

El que veiem és que és el propi programa el que orquestra els passos i va cridant els mètodes que necessita. Utilitzant la terminologia de Martin Fowler, diem que aquest codi està "in control" o controlat.

Ara bé, imaginem que en comptes d'utilitzar entrades de consola i prints fem el mateix utilitzant javaFX:

```
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class CaractersFX extends Application {
    private final CharacterService characterService;

    public CaractersFX() {
        this.characterService = new CharacterService();}

    public CaractersFX(CharacterService characterService) {
        this.characterService = characterService;
    }

    @Override
    public void start(Stage primaryStage) {
        Label lblPregunta = new Label("Quin és el teu nom?");
        TextField txtNombre = new TextField();
        txtNombre.setMaxWidth(300);

        Button btnConvertir = new Button("Convertir a majúscules");
        Label lblMayusculas = new Label();

        Label lblCharPregunta = new Label("Introdueix un caràcter a cercar:");
        TextField txtChar = new TextField();
        txtChar.setMaxWidth(30);
        
        TextFormatter<String> textFormatter = new TextFormatter<>(change -> 
            change.getControlNewText().length() <= 1 ? change : null);
        txtChar.setTextFormatter(textFormatter);

        Button btnBuscar = new Button("Buscar");
        Label lblResultado = new Label();

        // Evento para convertir a mayúsculas
        btnConvertir.setOnAction(e -> 
            lblMayusculas.setText("Nom en majúscules: " + 
                characterService.convertToUpperCase(txtNombre.getText()))
        );

        // Evento para buscar el carácter
        btnBuscar.setOnAction(e -> {
            String charInput = txtChar.getText();
            if (charInput.length() == 1) {
                char c1 = charInput.charAt(0);
                long count = characterService.countCharacterOccurrences(
                txtNombre.getText(), c1);
                lblResultado.setText("El teu nom té " + count + " caràcter(s) '"
                + c1 + "'");
            } else {
                lblResultado.setText("Introdueix només un caràcter.");
            }
        });

        // Configuración de la interfaz
        VBox root = new VBox(10, lblPregunta, txtNombre, btnConvertir, lblMayusculas, 
                             lblCharPregunta, txtChar, btnBuscar, lblResultado);
        root.setAlignment(Pos.CENTER);
        root.setPrefSize(400, 300);

        // Escena y Stage
        Scene scene = new Scene(root);
        primaryStage.setTitle("Aplicació Caràcters");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        CharacterService service = new CharacterService();
        CaractersFX app = new CaractersFX(service);
        Application.launch(app.getClass(), args);
    }
}
```

On fixeu-vos que s'injecta per constructor una classe de servei anomenada CharacterService, la qual conté els mètodes de passar a majúscules o comptar caràcters, és a dir, en certa manera, la lògica d'aplicació:

```
public class CharacterService {
    public String convertToUpperCase(String name) {
        return name.toUpperCase();
    }

    public long countCharacterOccurrences(String name, char character) {
        return name.toLowerCase().chars().filter(ch -> ch == character).count();
    }
}
```

A banda de la quantitat de línies de codi i mètodes, el flux de control dels dos codis és diferent. En el cas particular del codi fet amb javaFX, les funcionalitats de convertir a majúscules o comptar caràcters depenen de que l'usuari premi els corresponents botons i els botons utilitzin un framework per executar aquests mètodes/funcionalitats. El control i l'orquestració de com es van executant els mètodes no el té el propi codi sinó el framework. Per tant el que hem fet és invertir el control.  

**La inversió de control (IoC) és un principi de l'enginyeria del software el qual transfereix el control d'objectes o parts d'un programa a un contenidor o framework, en comptes de fer que sigui el propi programa el que orquestri i controli tot.**

Aquest principi també es coneix com **Principi de Hollywood** el qual ve caracteritzat per aquesta famosa frase:

![images](https://hackmd.io/_uploads/H19dSiujJx.jpg)

Font de la imatge: [Dzone.com: the hollywood principle](https://dzone.com/articles/the-hollywood-principle)

L'article original de Martin Fowler el trobareu aquí: [InversionOfControl](https://martinfowler.com/bliki/InversionOfControl.html).

## Injecció de dependències (DI)

La injecció de dependències (**DI**) és un dels models amb el qual podem implementar la inversió de control (IoC). 

Altres maneres d’implementar-la diferents són: Strategy desing pattern, localitzador de serveis (service locator) o pels patrons de factoria.

**DI** el que fa és invertir el control mitjançant les dependències entre els diferents objectes. D’aquesta forma el codi de l’aplicació apareix més desacoblat, els objectes s’injecten sols quan són necessaris.

### Exemple de codi que no utilitza DI

Amb les conegudes classes **Usuari** i **Incidencia**, el plantejament sense fer servir DI és ben simple: La classe Incidencia té una dependència directa i explícita amb la classe Usuari. Vegem com:

**Classe Usuari**

```
public class Usuari {

    private String nom;
    private String email;

    public Usuari() {}

    public Usuari(String nom, String email) {
        this.nom = nom;
        this.email = email;
    }

    public String getNom() {
        return nom;
    }

    public void setNom(String nom) {
        this.nom = nom;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public void mostrarInfo() {
        System.out.println("Usuari: " + nom + " | Email: " + email);
    } 
}
```

**Classe Incidencia**

```
public class Incidencia {
    private int id;
    private String descripcion;
    private Usuari u1;

    public Incidencia(int id, String descripcion) {
        this.id = id;
        this.descripcion = descripcion;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getDescripcion() {
        return descripcion;
    }

    public void setDescripcion(String descripcion) {
        this.descripcion = descripcion;
    }

    public void mostrarDetalle() {
        u1 = new Usuari();
        u1.setNom("Eric A");
        u1.setEmail("eric_a@iticbcn.cat");
        u1.mostrarInfo();
        System.out.println("Incidencia #" + id + ": " + descripcion);
    }
}
```

I ara la classe app, instanciant una nova incidència i mostrant-la. Fixeu-vos que la classe usuari no apareix de forma explícita:

```
public class App {
    public static void main(String[] args) throws Exception {
        
        Incidencia inc1 = new Incidencia(1, "Configuració errònia xarxa");

        inc1.mostrarDetalle();
    }
}
```

### DI amb constructor

Una de les coses que hem de fer per aplicar la DI és desacoblar les classes, de tal forma que en aquest cas Incidencia no depengui de forma tan directa d'Usuari.

Per fer-ho injectarem Usuari dins el constructor de la classe Incidencia:

```
public class Incidencia {
    private int id;
    private String descripcion;
    private Usuari u1;

    public Incidencia(int id, String descripcion, Usuari u1) {
        this.id = id;
        this.descripcion = descripcion;
        this.u1 = u1;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getDescripcion() {
        return descripcion;
    }

    public void setDescripcion(String descripcion) {
        this.descripcion = descripcion;
    }

    public void mostrarDetalle() {
        u1.mostrarInfo();
        System.out.println("Incidencia #" + id + ": " + descripcion);
    }
}
```

I ara tornem a la classe app i creem un usuari, el qual passarem a incidencia:

```
public class App {
    public static void main(String[] args) throws Exception {

        Usuari user1 = new Usuari("Eric A", "eric_a@iticbcn.cat");
        
        Incidencia inc1 = new Incidencia(1, "Configuració errònia xarxa", user1);

        inc1.mostrarDetalle();
    }
}
```

De fet, tornant al codi anterior amb javaFX, ja estàvem injectant per constructor la classe CharacterService dins CaractersFX.

### DI per setter (o per mètodes)

En aquest cas modificarem la classe Incidencia per tal que usuari surti del constructor i incorporem el mètode setU1 (o setUsuari si es vol), el qual modificarà aquesta propietat sobre incidència:

```
public class Incidencia {
    private int id;
    private String descripcion;
    private Usuari u1;

    public Incidencia(int id, String descripcion) {
        this.id = id;
        this.descripcion = descripcion;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getDescripcion() {
        return descripcion;
    }

    public void setDescripcion(String descripcion) {
        this.descripcion = descripcion;
    }

    public Usuari getU1() {
        return u1;
    }

    public void setU1(Usuari u1) {
        this.u1 = u1;
    }

    public void mostrarDetalle() {
        u1.mostrarInfo();
        System.out.println("Incidencia #" + id + ": " + descripcion);
    }
}
```

Sobre la classe App el canvi és ben senzill:

```
public class App {
    public static void main(String[] args) throws Exception {

        Usuari user1 = new Usuari("Eric A", "eric_a@iticbcn.cat");
        
        Incidencia inc1 = new Incidencia(1, "Configuració errònia xarxa");

        inc1.setU1(user1);

        inc1.mostrarDetalle();
    }
}
```

D'aquesta forma també desacoblem Incidencia i Usuari.

### DI amb interfícies

La injecció de dependències amb interfícies no és res que no haguem fet fins ara, especialment quan aplicàvem el patró DAO.

Imaginem ara que qualsevol usuari pot fer tant incidències com peticions. Tant unes com altres implementen una interfície que anomenarem Comandes.

Comandes té un mètode anomenat assignarComplexitat, el qual com veurem tot seguit té diferent implementació a peticions i comandes. 

Per altra banda tindrem una nova classe anomenada Cost, la qual té un mètode de calcular el cost segons si és incidència o petició.

Així, les interfícies i classes queden com segueix:

**Interfície Comandes**

```
public interface Comandes {
    public int assignarComplexitat (double numHores);
    public String mostrarIdDescripcio();
}
```

**Classe Petició (implementa Comandes)**

```
public class Peticio implements Comandes {
    private int idComanda;
    private String descripcioComanda;
    private Usuari u1;
    private boolean aprovada;

    public Peticio(int idComanda, String descripcioComanda, boolean aprovada, Usuari u1) {
        this.idComanda = idComanda;
        this.descripcioComanda = descripcioComanda;
        this.aprovada = aprovada;
        this.u1 = u1;
    }

    @Override
    public String mostrarIdDescripcio() {
        return Integer.toString(idComanda) + " - " +  descripcioComanda ;
    }

    @Override
    public int assignarComplexitat(double numHores) {
        
        int complexitat;

        if (numHores >= 1.0 && numHores <3.0) {
            complexitat = 1;
        } else if (numHores >= 3.0 && numHores <9.0) {
            complexitat = 2;
        } else if (numHores >= 9.0) {
            complexitat = 3;
        } else {
            System.out.println("No es pot deteminar la complexitat");
            complexitat = 0;
        }

        return complexitat;
    }


    public int getIdComanda() {
        return idComanda;
    }

    public void setIdComanda(int idComanda) {
        this.idComanda = idComanda;
    }

    public String getDescripcioComanda() {
        return descripcioComanda;
    }

    public void setDescripcioComanda(String descripcioComanda) {
        this.descripcioComanda = descripcioComanda;
    }

    public Usuari getU1() {
        return u1;
    }

    public void setU1(Usuari u1) {
        this.u1 = u1;
    }

    public boolean isAprovada() {
        return aprovada;
    }

    public void setAprovada(boolean aprovada) {
        this.aprovada = aprovada;
    }
}
```

**Classe Incidència (implementa Comandes)**

```
public class Incidencia implements Comandes {
    private int id;
    private String descripcion;
    private Usuari u1;

    public Incidencia(int id, String descripcion, Usuari u1) {
        this.id = id;
        this.descripcion = descripcion;
        this.u1 = u1;
    }

    @Override
    public String mostrarIdDescripcio() {
        return Integer.toString(id) + " - " +  descripcion ;
    }

    @Override
    public int assignarComplexitat(double numHores) {
        
        int complexitat;

        if (numHores >= 0.5 && numHores <2.0) {
            complexitat = 1;
        } else if (numHores >= 2.0 && numHores <6.0) {
            complexitat = 2;
        } else if (numHores >= 6.0) {
            complexitat = 3;
        } else {
            System.out.println("No es pot deteminar la complexitat");
            complexitat = 0;
        }
        
        return complexitat;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getDescripcion() {
        return descripcion;
    }

    public void setDescripcion(String descripcion) {
        this.descripcion = descripcion;
    }

    public Usuari getU1() {
        return u1;
    }

    public void setU1(Usuari u1) {
        this.u1 = u1;
    }

    public void mostrarDetalle() {
        u1.mostrarInfo();
        System.out.println("Incidencia #" + id + ": " + descripcion);
    }
}
```

**Classe Cost**

Fixeu-vos que en aquesta classe estem injectant la dependència amb Comandes:

```
public class Cost {

    private Comandes comanda;

    public Cost (Comandes comanda) {
        this.comanda = comanda;
    }

    public double CalcularCost(double tarifa, double hores) {
        double cost = 0;

        cost = tarifa * hores *  (comanda.assignarComplexitat(hores)/4.0);

        return cost;
    }

    public Comandes getComanda() {
        return comanda;
    }

    public void setComanda(Comandes comanda) {
        this.comanda = comanda;
    }

}
```

I ja per últim la classe d'aplicació on mostrem el cost segons si és incidència o petició, el seu nombre d'hores i la tarifa:

```
public class App {
    public static void main(String[] args) throws Exception {

        Usuari user1 = new Usuari("Eric A", "eric_a@iticbcn.cat");
        
        Comandes inc1 = new Incidencia(1, "Configuració errònia xarxa",user1);
        Comandes pet1 = new Peticio(1, "Aprovar desplaçament"
        , false,user1);

        Cost cost = new Cost(inc1);

        System.out.println("El cost de la incidència: " 
        + inc1.mostrarIdDescripcio() + " és: " + cost.CalcularCost(12, 7) + " eur" );

        cost.setComanda(pet1);

        System.out.println("El cost de la petició: " 
        + pet1.mostrarIdDescripcio() + " és: " + cost.CalcularCost(12, 7) + " eur" );
        
    }
}
```

Tots aquests codis els podreu trobar al repositori: [repositoris - UF4](https://github.com/atalens1/UF4---repositoris)