Skip to content
OBJETOS - Inyección de dependencias

Caso 3 - Inyección de dependencias

Caballeros de la mesa redonda

Tomado de Spring in Action

Añadir pruebas unitarias a la solución siguiente:

public class KnightOfTheRoundTable {
  private String name;
  private HolyGrailQuest quest;
  public KnightOfTheRoundTable(String name) {
    this.name = name;
    quest = new HolyGrailQuest();
  }
  public HolyGrail embarkOnQuest() throws GrailNotFoundException {
    return quest.embark();
  }
}

public class HolyGrailQuest {
  public HolyGrailQuest() {}
  public HolyGrail embark() throws GrailNotFoundException {
    HolyGrail grail = null;
    // Look for grail
    ...
    return grail;
  }
}

Construir pruebas con jUnit 3

¿Dónde está el acoplamiento?

import junit.framework.TestCase;
public class KnightOfTheRoundTableTest extends TestCase {
  public void testEmbarkOnQuest() throws GrailNotFoundException {
    KnightOfTheRoundTable knight =
        new KnightOfTheRoundTable("CruzadoMagico");
    HolyGrail grail = knight.embarkOnQuest();
    assertNotNull(grail);
    assertTrue(grail.isHoly());
  }
}
  • Instanciación de HolyGrail

  • Cada vez que se prueba KnightOfTheRoundTable, también se prueba HolyGrailQuest.

  • No se puede pedir a HolyGrailQuest que se comporte de otra forma (v.g. devolver null o elevar una excepción)

Ocultar la implementación detrás de una interfaz

public interface Knight {
  Object embarkOnQuest() throws QuestFailedException;
}

public class KnightOfTheRoundTable implements Knight {
  private String name;
  private Quest quest;
  public KnightOfTheRoundTable(String name) {
    this.name = name;
    quest = new HolyGrailQuest();
  }
  public Object embarkOnQuest() throws QuestFailedException {
    return quest.embark();
  }
}

public interface Quest {
  abstract Object embark()
    throws QuestFailedException;
}

public class HolyGrailQuest implements Quest {
  public HolyGrailQuest() {}
  public Object embark() throws QuestFailedException {
    // Do whatever it means to embark on a quest
    return new HolyGrail();
  }
}
  • El Knight aún recibe un tipo específico de Quest
  • ¿Debe ser el caballero responsable de obtener un desafío?

Inyectar dependencias

public class KnightOfTheRoundTable implements Knight {
  private String name;
  private Quest quest;
  public KnightOfTheRoundTable(String name) {
    this.name = name;
  }
  public Object embarkOnQuest() throws QuestFailedException {
    return quest.embark();
  }
  public void setQuest(Quest quest) {
    this.quest = quest;
  }
}
  • El caballero no es el responsable de averiguar su misión.
  • El caballero sólo sabe de su misión a través de la interfaz Quest.
  • El caballero recibe la misión (se le inyecta) a través de setQuest()
  • Puede asignársele cualquier implementación de Quest (HolyGrailQuest, RescueDamselQuest, etc.)

Construcción con spring

A través de un fichero de configuración XML le indicamos los valores inyectables:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
  "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
  <bean id="quest"
    class="HolyGrailQuest"/>
  <bean id="knight"
    class="KnightOfTheRoundTable">
    <constructor-arg>
      <value>CruzadoMagico</value>
    </constructor-arg>
    <property name="quest">
      <ref bean="quest"/>
    </property>
  </bean>
</beans>

La inyección de la dependencia concreta la hace el contenedor (spring en este ejemplo):

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;

public class KnightApp {
  public static void main(String[] args) throws Exception {
    BeanFactory factory = new XmlBeanFactory(new FileInputStream("knight.xml"));
    KnightOfTheRoundTable knight = (KnightOfTheRoundTable) factory.getBean("knight");
    knight.embarkOnQuest();
  }
}

Ejemplo: Logger

También se puede inyectar la dependencia en el constructor.

import java.util.logging.Logger;

public class MyClass {
  private final static Logger logger;
  public MyClass(Logger logger) {
      this.logger = logger;
      // write an info log message
      logger.info("This is a log message.")
  }
}

Un contenedor de dependencias en el framework debe responsabilizarse de crear las instancias de Logger e inyectarlas en su sitio (normalmente vía reflexión o introspección)

Implementación final de la Orquesta v0.8

Los new de PruebaOrquesta de la versión v0.7 siguen introduciendo dependencias de PruebaOrquesta con respecto a los tipos concretos de Instrumento.

A través de un fichero de configuración orquesta.xml de Spring le indicamos los valores inyectables:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
  "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
  <bean id="trompeta"
    class="Viento"/>
  <bean id="violin"
    class="Cuerda"/>
  <bean id="tambor"
    class="Percusion"/>
  <bean id="viola"
    class="Cuerda"/>

  <bean id="cuarteto"
    class="Orquesta">
    <property name="instrumento1">
      <ref bean="trompeta"/>
    </property>
    <property name="instrumento2">
      <ref bean="violin"/>
    </property>
    <property name="instrumento3">
      <ref bean="viola"/>
    </property>
    <property name="instrumento4">
      <ref bean="tambor"/>
    </property>    
  </bean>
</beans>

La inyección de la dependencia concreta la hace el contenedor (Spring en este ejemplo):

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
public class PruebaOrquesta {
  public static void main(String[] args) throws Exception {
    BeanFactory factory =
      new XmlBeanFactory(new FileInputStream("orquesta.xml"));
    Orquesta orquesta =
      (Orquesta) factory.getBean("cuarteto");
    for (Instrumento i: orquesta)
           orquesta.afinar(i);
    orquesta.tocar();
  }
}

Dependencias en Java

JSR 330 es un estándar de Java para describir las dependencias de una clase con @Inject y otras anotaciones. Hay diversas implementaciones de JSR 330.

public class MyPart {
  @Inject private Logger logger;
  // inject class for database access
  @Inject private DatabaseAccessClass dao;
  @Inject
  public void createControls(Composite parent) {
    logger.info("UI will start to build");
    Label label = new Label(parent, SWT.NONE);
    label.setText("Eclipse 4");
    Text text = new Text(parent, SWT.NONE);
    text.setText(dao.getNumber());
  }
}

Esta clase sigue usando new para ciertos elementos de la interfaz. Esto significa que no pensamos reemplazarlos ni siquiera para hacer pruebas.

Ejercicio: Identificador de BankAccount con inyección de dependencias

Supongamos que queremos obtener un listado ordenado por fecha de creación de todas las cuentas bancarias.

¿Cómo afecta este cambio a la versión de BankAccount ya implementada con JDK 1.5?
Resolver mediante inyección de dependencias

BankAcccount.java:

import java.util.*;
import java.io.*;
import java.time.*;

public final class BankAccount implements Comparable<BankAccount> {
  private final String id;
  private LocalDate creationDate;
  private Comparator comparator;

  public BankAccount(String number) {
    this.id = number;
    comparator = new BankAccountComparatorById();
  }

  public LocalDate getCreationDate() {
    return creationDate;
  }

  public void setCreationDate(LocalDate date) {
    this.creationDate = date;
  }

  public String getId() {
    return id;
  }

  public void setComparator(Comparator cmp) {
    comparator = cmp;
  }

  @Override
  public int compareTo(BankAccount other) {
    if (this == other)
      return 0;
    assert this.equals(other) : "compareTo inconsistent with equals.";
    return comparator.compare(this, other);
  }

  @Override
  public boolean equals(Object other) {
    if (this == other)
      return true;
    if (!(other instanceof BankAccount))
      return false;
    BankAccount that = (BankAccount) other;
    return this.id.equals(that.getId());
  }

  @Override
  public String toString() {
    return id.toString();
  }
}

BankAcccountComparatorById.java:

import java.util.*;

class BankAccountComparatorById implements Comparator<BankAccount> {
    public int compare(BankAccount o1, BankAccount o2) {
        return o1.getId().compareTo(o2.getId());
    }
}

BankAcccountComparatorByCreationDate.java:

import java.util.*;

class BankAccountComparatorByCreationDate implements Comparator<BankAccount> {
    public int compare(BankAccount o1, BankAccount o2) {
        return o1.getCreationDate().compareTo(o2.getCreationDate());
    }
}

Ahora podría definirse una anotación del tipo @comparator(BankAccountComparatorById.className) o @compareById que inyecte a BankAccount una dependencia BankAccountComparatorById en BankAccount.comparator.

Creación de anotaciones en Java

Decoradores en TypeScript

  • Los decoradores de TypeScript son una forma de modificar programáticamente la definición de una clase.

  • La definición de una clase describe la forma de la clase, es decir, sus métodos y propiedades. Sólo cuando se instancie la clase, estas propiedades y métodos estarán disponibles.

  • Los decoradores permiten inyectar código en la definición real de una clase.

Pueden emplearse sobre:

  • definiciones de clase
  • definiciones de propiedades
  • definiciones de funciones
  • parámetros de métodos

Los decoradores de TypeScript se llaman atributos en C# y anotaciones en Java

Los decoradores de TypeScript son una característica experimental del compilador y se han propuesto como parte del estándar ECMAScript 7. Deben activarse modificando el parámetro experimentalDecorators en tsconfig.json:

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es3",
    "sourceMap": true,
    "experimentalDecorators": true
  },
  "exclude": [
    "node_modules"
  ]
}

Declaración

function simpleDecorator(constructor: Function) {
  console.log('simpleDecorator called.');
}

Uso

@simpleDecorator
class ClassWithSimpleDecorator {

}
¿Cuál es la salida del siguiente código TypeScript?
simpleDecorator called.
instance_1 : [object Object]
instance_2 : [object Object]
let instance_1 = new ClassWithSimpleDecorator();
let instance_2 = new ClassWithSimpleDecorator();
console.log(`instance_1: ${instance_1}`);
console.log(`instance_2 : ${instance_2}`);

Decoradores mútiples

¿Cuál es la salida del siguiente código TypeScript?
secondDecorator called.
simpleDecorator called.
instance_1 : [object Object]
function simpleDecorator(constructor: Function) {
  console.log('simpleDecorator called.');
}

function secondDecorator(constructor: Function) {
  console.log('secondDecorator called.')
}

@simpleDecorator
@secondDecorator
class ClassWithMultipleDecorators {
}

let instance_1 = new ClassWithMultipleDecorators();
console.log(`instance_1: ${instance_1}`);

Factorías de decoradores

  • Los decoradores pueden aceptar parámetros
  • Una factoría de decoradores es una función que devuelve el propio decorador.
Ejemplo de factoría de decoradores
function decoratorFactory(name: string) {
  return function (constructor: Function) {
    console.log(`decorator function called with: ${name}`);
  }
}

@decoratorFactory('testName')
class ClassWithDecoratorFactory {
}

Salida:

decorator function called with: testName

Tipos de decoradores

  • Decoradores de clases
  • Decoradores de propiedades
  • Decoradores de propiedades estáticas
  • Decoradores de métodos
  • Decoradores de parámetros

Lectura recomendada

Nathan Rozentals: Mastering TypeScript, Packt Publishing, 2nd edition, 2017