Home > Desarrollo, Librerías/Frameworks, Testing > Problema con Singletons y patron estrategia

Problema con Singletons y patron estrategia

El patron estrategia es uno de los que mas “ifs” te pueden quitar en el codigo. Un ejemplo sencillo de como funciona seria imaginar una partida de ajedrez. Sin el patron estrategia, cada vez que un jugador moviese una ficha, el programa tendria que comprobar de que ficha se trata para calcular sus posibles movimientos. Usando el patron estrategia, es la propia ficha la que sabe como moverse y asi nos ahorramos hacer las comprobaciones (ifs).

El problema es que hay veces que las diferentes estrategias cuestan mucho instanciarlas. Por ejemplo porque hace alguna llamada HTTP, o tiene que ir a base de datos a recuperar informacion al levantarse, etc. Este tipo de clases tan costosas suele ser buena idea hacerlas Singleton. El problema de los singleton, es que tienen que ser inmutables (no deberiamos modificar su contenido) o podriamos tener problemas de concurrencia. En nuestro ejemplo del ajedrez, esto supondria que tendriamos que pasar por parametros de metodo las coordenadas a donde queremos mover, en vez de por constructor. Esto seria la solucion definitiva en una partida de ajedrez, ya que solo se necesitan 2 coordenadas para posicionar una figura en el espacio, pero si se tratase del parchis necesitariamos solo 1, ya que es unidireccional. Este detalle no parece importante, pero imagina que estas en un proyecto bastante grande y tienes que estar ignorando parametros del metodo en funcion de si se trata de ajedrez o parchis.

Estaba interesado en dar una solucion flexible al problema del patron estrategia con singletons. La idea del siguiente ejemplo, es hacer que las estrategias sean lo mas limpias posibles, sin exceso de parametros y que sean singletons.

El ejemplo consiste en una interfaz, con dos implementaciones. Cada implementacion es singleton y maneja un objeto de datos diferente (DTO). He usado Guice para ello:

package com.tododev.guice;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.logging.Logger;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.http.HttpServletRequest;

import org.junit.Test;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Provider;
import com.google.inject.Provides;

public class StrategySingletonTest {

	private static final Logger log = Logger.getLogger(StrategySingletonTest.class.getName());
	private final Injector injector = Guice.createInjector(new MyModule());
	private final static String PARAM_1 = "paramDto1";
	private final static String PARAM_2 = "paramDto2";
	
	@Test
	public void test(){
		Logic<?> logicDto1 = injector.getInstance(LogicDto1.class);
		logicDto1.watchDto();
		
		// Comprobamos que aunque son singletons, los DTO's que instancian son diferentes
		assertNotSame(logicDto1.getInstance(), logicDto1.getInstance());
		
		Logic<?> logicDto1SameInstance = injector.getInstance(LogicDto1.class);
		
		// Comprobamos que son la misma instancia
		assertEquals(logicDto1, logicDto1SameInstance);
		
		
		Logic<?> logicDto2 = injector.getInstance(LogicDto2.class);
		logicDto2.watchDto();
		
	}
	
	
	// Dto que manejara la implementacion LogicDto1
	private static class Dto1{
		private final String field;
		public Dto1(String field) {
			this.field = field;
		}
	}
	
	// Dto que manejara la implementacion LogicDto2
	private static class Dto2{
		private final int integer;
		public Dto2(int integer) {
			this.integer = integer;
		}
	}
	
	// Definimos la forma de instanciarse Dto1
	private static class Dto1Provider implements Provider<Dto1>{

		private final HttpServletRequest request;
		
		@Inject
		public Dto1Provider(HttpServletRequest request){
			this.request=request;
		}
		
		@Override
		public Dto1 get() {
			return new Dto1(request.getParameter(PARAM_1));
		}
		
	}
	
	// Definimos la forma de instanciarse Dto2
	private static class Dto2Provider implements Provider<Dto2>{

		private final HttpServletRequest request;
		
		@Inject
		public Dto2Provider(HttpServletRequest request){
			this.request=request;
		}
		
		@Override
		public Dto2 get() {
			return new Dto2(Integer.parseInt(request.getParameter(PARAM_2)));
		}
		
	}
	
//	Decimos que los DTOs se consiguen a traves de los providers y
//	 introducimos las implementaciones de las interfaces
//	 para que guice sepa que existen
	private static class MyModule extends AbstractModule{

		@Override
		protected void configure() {
			bind(Dto1.class).toProvider(Dto1Provider.class);
			bind(Dto2.class).toProvider(Dto2Provider.class);
			bind(LogicDto1.class);
			bind(LogicDto2.class);
		}
		
//		Devuelve un mock de HttpServletRequest, que es con lo que
//		 formaremos los DTOs
		@Provides
		public HttpServletRequest getRequest(){
			HttpServletRequest request = mock(HttpServletRequest.class);
			when(request.getParameter(PARAM_1)).thenReturn("Jorge");
			when(request.getParameter(PARAM_2)).thenReturn("28");
			return request;
		}
		
	}
	
	private static interface Logic<DTO> {
		void watchDto();
		DTO getInstance();
	}
	
	@Singleton
	private static class LogicDto1 implements Logic<Dto1>{

		@Inject
		private Provider<Dto1> provider;
		
		@Override
		public void watchDto() {
			Dto1 dto = getInstance();
			log.info("I'm "+dto+" and my name is: "+dto.field);
		}

		@Override
		public Dto1 getInstance() {
			return provider.get();
		}
		
	}
	
	@Singleton
	private static class LogicDto2 implements Logic<Dto2>{

		@Inject
		private Provider<Dto2> provider;
		
		@Override
		public void watchDto() {
			Dto2 dto = getInstance();
			log.info("I'm "+dto+" and I am: "+dto.integer);
		}

		@Override
		public Dto2 getInstance() {
			return provider.get();
		}
		
	}
	
}

Esta es la salida que obtendremos:
I’m com.tododev.guice.StrategySingletonTest$Dto1@6ae441ee and my name is: Jorge
I’m com.tododev.guice.StrategySingletonTest$Dto2@5ef4f91d and I am: 28

Este es el grafo de las dependencias que tiene el inyector:

example

Advertisements
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: