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 pruebaHolyGrailQuest. - 
No se puede pedir a
HolyGrailQuestque 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 
Knightaún recibe un tipo específico deQuest - ¿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
- Ejemplo de cómo crear una anotación a medida 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