Adaptadores en Java 8

Enchufe americano

Adaptadores en Java 8

Pongámonos en situación. Acabas de comprar tu flamante portátil con tropecientos núcleos y su disco duro de estado sólido con la capacidad de almacenar el catálogo completo de cualquier plataforma de películas on-line. Lo has estado usando en casa sin ningún problema, pero cuando sales de viaje te encuentras con un problema. No puedo usar el portátil porque no puedo enchufarlo a la red eléctrica de otros países, ¡no tengo adaptador!

El problema tiene fácil solución, después de comprobar que la fuente de alimentación trabaja en los márgenes de voltaje de otros países, vas al bazar más cercano y compras el adaptador correspondiente para poder conectar tu portátil.

Todos los que alguna vez han trabajado en aplicaciones en las que intervienen distintos equipos o consumen datos externos, se han encontrado con la misma situación. Mis objetos en el dominio de la aplicación (mi portátil en casa) funcionan sin problemas. En el momento que tenemos que interactuar con otras interfaces, necesitamos crear adaptadores.

Veamos un ejemplo de un adaptador

Supongamos una aplicación en la que trabajamos con mercados financieros y tenemos un objeto para representar los contratos representados por la interfaz.

public interface ContractInfo {
       public String getContractId();
       public Date getMaturity();
}

Hasta la fecha siempre hemos trabajado con un mercado por lo que únicamente tenemos la implementación.

public class MyMarketContractInfo implements ContractInfo{
       private String contractId;
       private Date maturity;
       /** constructor  */
       public String getContractId(){return contractId;}
       public Date getMaturity(){return maturity;};
}

Sin embargo al cabo de unos años nos encontramos con que tenemos que recibir contratos desde otros mercados, y se reciben a través de un API cerrado donde los contratos vienen a través de la interfaz.

public interface NewMarketContractInfo {
       public String getSymbolId();
       public Date getEndDate();
}

En el momento que vemos la nueva interfaz, nos invade un momento de pánico. ¿Cómo podré trabajar con los nuevos contratos? Después de controlar la respiración te das cuenta de que con un simple adaptador podemos leer los nuevos contratos sin problema.

 public class MyNewMarketContractInfoAdapter implements ContractInfo{
       private NewMarketContractInfo newContract;

       /** constructor  */
       public String getContractId(){return newContract.getSymbolId();}
       public Date getMaturity(){return newContract.getEndDate();}};
}

Ya tengo mi adaptador, mi portátil funcionando, cuando me llama mi jefe y me dice “Tienes que visitar cada uno de los países que existen en el mundo”…

No mi jefe no es tan malo, me llama mi jefe y me dice “Tienes que visitar al menos un país del mundo donde tengan distintas tomas de corriente” y la dirección de la Wikipedia donde vienen https://es.wikipedia.org/wiki/Anexo:Enchufes,_voltajes_y_frecuencias_por_pa%C3%ADs

Después de agradecerle para mis adentros la oportunidad que me da de viajar <ironia>off</ironia> Bajo al bazar y me encuentro con lo que necesitaba y que a todo amante de los gadgets nos gusta. “El adaptador universal”

Gracias a las nuevas características de Java 8 como son los métodos por defecto en las interfaces y la programación funcional, es más fácil que nunca crear adaptadores universales.

Volvamos a nuestro ejemplo

Mi jefe me llama y me dice “Nuestra plataforma tiene que soportar los mercados de todos los países del mundo”.

Volvemos al momento de pánico. ¿Tengo que implementar un adaptador para cada mercado?

Java 8 viene al rescate

Añadiendo un método estático podemos convertir cualquier objeto a “ContractInfo”

public interface ContractInfo {
       public String getContractId();
       public Date getMaturity();
       static ContractInfo newInstance(String contractId, Date maturity){
              return new ContractInfo() {
                    @Override
                    public String getContractId() {return contractId;}                
                    @Override
                    public Date getMaturity() {return maturity;}
             };
       }
}

Y gracias a Java 8 podemos transformar una lista de contratos de una forma rápida.

List<NewMarketContractInfo> newContracts=strangeApi.getNewMarketContracts();
List<ContractInfo> contracts= newContracts.stream()
       .map(p-> ContractInfo.newInstance(p.getSymbolId(),p.getEndDate()))
       .collect(Collectors.toList());

Estamos tan contentos con nuestro adaptador cuando un nuevo mercado nos suministra los contratos a través de una interfaz exótica.

public interface FunnyContract {
       public String getSymbolId ();
       public int getDay();
       public String getMonth();
       public int getYear();
}

Cualquiera que haya trabajado en Java en aplicaciones en tiempo real se dará cuenta que la conversión a Date de los datos de esta interfaz puede ser un poco lenta. Además nuestra aplicación trabaja sobre un conjunto de contratos que deben ser filtrados. Nuestro módulo de filtrado trabaja sobre la interface ContractInfo, por lo que transformar las fechas de todos los contratos aunque luego van a ser  filtrados puede ser algo ineficiente.

¿Cómo podemos retrasar la conversión hasta el momento de uso?

Nuevamente Java 8 viene al rescate

static ContractInfo newInstance(Supplier<String> contractId, Supplier<Date> maturity){
        return new ContractInfo() {
               @Override
               public String getContractId() {return contractId.get();}
               @Override
               public Date getMaturity() {return maturity.get();}
        };
 }

De esta forma el adaptador se crea con el método que tiene que usar para convertir el dato y no con el dato en sí.

En algún sitio tendremos que tener la función:

       public Date maturityDateFromFunnyContract(FunnyContract c){………}

Y para hacer generar nuestros ContractInfo de una manera “Lazy” haríamos:

List< FunnyContract > newContracts=strangeApi.getNewFunnyContracts();
List<ContractInfo> contracts= newContracts.stream()
       .map(p-> ContractInfo.newInstance(p::getSymbolId,()->this.
              maturityDateFromFunnyContract(p)))
       .collect(Collectors.toList());