Número 73
22 de novembro de 2023

Inxección de dependencias

Hoxe vou falar da inxección de dependencias, que é un patrón que ten unha mala fama totalmente inmerecida. A maioría da xente oe falar do tema e pensa nun deses frameworks terribles coma Spring (Deus me libre), pero na realidade, a inxección de dependencias é moi simple e moi útil, e moitos de vós xa a tedes posto en práctica incluso sen vos decatar.

Un doutor da Peste Negra cunha xiringa xigante.

Moitas veces, o noso código ten que acceder a recursos externos: por exemplo, ten que ler ficheiros, facer consultas RPC, acceder a unha base de datos ou interpretar un ficheiro XML. Dicimos que eses recursos externos son “dependencias” do noso código porque, para funcionar correctamente, o código depende deles.

Normalmente escribimos o noso código de maneira que accede directamente ás súas dependencias. Por exemplo: cando temos que ler datos dun ficheiro, poñemos unha chamada a open, despois lemos del e finalmente facemos o que queriamos facer co contido do ficheiro.

int LerChistesDeFarruco(char *name, struct Chistes *out) {
  int fd = open(name, O_RDONLY);
  if (fd < 0) return -1;
  char buf[65536];
  int bytes = read(fd, buf, sizeof(buf));
  // interpretar o contido de "buf" de lonxitude "bytes"
  close(fd);
  return 0;
}

Cando facemos inxección de dependencias, o noso código non “adquire” as dependencias directamente, senón que as recibe de fóra. É dicir: a función non abre e le o ficheiro, senón que recibe o contido.

int LerChistesDeFarruco(char *buf, size_t bytes, struct Chistes *out) {
  // interpretar o contido de "buf" de lonxitude "bytes"
  return 0;
}

Se algunha vez fixestes algo así, usastes a inxección de dependencias. Parabéns!

A principal avantaxe da inxección de dependencias é que fai o noso código máis reutilizable. No exemplo de enriba, a función orixinal só servía para interpretar un ficheiro que existía no disco, pero a versión modificada pode interpretar datos procedentes dun ficheiro, da web, dunha base de datos e de calquera outro sitio. Para máis ben, escribir tests unitarios para a segunda función é moito máis doado que para a primeira, xa que non é preciso crear ficheiros para que a función os lea.

Normalmente, os cambios que facemos cando queremos aplicar a inxección de dependencias non son tan radicais. O patrón máis habitual é substituír a creación dunha instancia por un argumento que recibe unha instancia xa creada.

// Antes
public class LectorDeChistes {
  public LectorDeChistes(String config) {
    this.XMLParser = new XMLParser(new XMLConfig(...));
  }
  // etc
}

// Despois
public class LectorDeChistes {
  public LectorDeChistes(XMLParser parser) {
    this.XMLParser = parser;
  }
  // etc
}

Igual pensades que a inxección de dependencias é unha invención moderna, pero vén de vello. No tempo no que toda a informática consistía en mainframes e os dinosauros dominaban a Terra, a maneira na que os programas accedían aos ficheiros era moi diferente a como o fan hoxe.

Hoxe, cando un programa quere ler datos dun ficheiro, só ten que abrilo usando o seu nome. Porén, daquela, os programas non podían facer iso, senón que tiñan que declarar que lían datos de dous ficheiros e escribían datos nun terceiro ficheiro, por exemplo. Despois, para poder usar o programa, un operador tiña que escribir unha configuración que asigna cada ficheiro a un espazo no disco, unha fita magnética ou un xogo de tarxetas furadas.

Non se parece moito isto á inxección de dependencias? No canto de abrir os seus propios ficheiros, os programas recibían os ficheiros xa abertos do sistema operativo. Grazas a isto, un programa deseñado para tarxetas furadas podía seguir funcionando, sen cambios, nun mainframe con discos magnéticos.

Para rematar, unha pequena curiosidade. Os programas feitos na linguaxe Pascal comezan cunha declaración PROGRAM que pode recibir, como parámetros opcionais, os nomes dos ficheiros de entrada e saída predeterminados:

PROGRAM OMeuPrograma (INPUT, OUTPUT);

Como podedes imaxinar, isto é un pequeno resto que sobrevive dos tempos nos que os programas tiñan que declarar os ficheiros que usaban. Hoxe en día, eses parámetros son opcionais, pero daquela, se querías acceder a outros ficheiros, había que declaralos aquí para que o operador os puidera configurar.