Patrón: Strategy / GoF

En esta entrada, no vamos a hablar de los patrones cloud como comúnmente hemos estado escribiendo en los últimos artículos. Vamos a ir hasta la categoría de patrones de comportamiento dentro de los conocidos Gang of Four para hablar de Strategy y veremos un ejemplo aprovechando las características de Spring como contenedor de beans.

El patrón Strategy define como se debe realizar el intercambio de mensajes entre diferentes objetos para realizar una tarea, aprovecha un grupo de algoritmos similares que pueden intercambiarse en tiempo de ejecución. En el libro “Design Patterns: Elements of Reusable Object-Oriented Software” abordan la definición de strategy como sigue:

The strategy pattern is used to create an interchangeable family of algorithms from which the required process is chosen at run-time.

Libro: Design Patterns: Elements of Reusable Object-Oriented Software

Funcionamiento general

El patrón strategy, esta guiado de forma general por el siguiente diagrama:

Los elementos principales son:

  • Una interfaz principal (o clase Abstracta/Padre) que define la estrategia general a realizar.
  • Un grupo de clases derivadas que implementan la estrategia acorde a casos particulares.
  • Una entidad de composición con la capacidad de acceder a las implementaciones de las estrategias, normalmente basándose en la interfaz principal.
  • En la entidad anterior hay cierta lógica con la capacidad de alternar de forma sencilla para invocar una estrategia en dependencia del contexto de ejecución.

Veamos a continuación un ejemplo un poco mas sencillo, aprovechando además las bondades de Spring como Bean Container.

Ejemplo de Strategy con Spring

En el siguiente ejemplo tomemos como caso de estudio una implementación de Robot, existiendo 3 tipos de Robots: Roomba, Humanoide y Kangoro, todos los robots implementan una forma de moverse: Roomba se “desliza”, Humanoid puede “caminar”, y Kangoro puede “saltar”.

Para este sencillo caso, lo que debemos implementar es una estrategia para obtener la FormaDeMoverse en dependencia del Robot que esté en el contexto actual.

Como puede notarse hemos agregado a la Interfaz además de getFormaMoverse el método getType(), lo usaremos para aprovechar las ventajas de Spring de la creación de Beans en el arranque para crear en el RobotContext una asociación entre el name y el Bean de cada implementación.

En este caso, usaremos para identificar a cada Robot un tipo enumerativo:

public enum RobotType {
    ROOMBA, KANGORO, HUMANOIDE
}

La definición de la interfaz y los Robots se pone debajo:

public interface IRobotStrategy {
    RobotType getType();
    String getFormaMoverse();
}
@Component
public class RobotHumanoide implements IRobotStrategy {
    @Override
    public RobotType getType() {
        return RobotType.HUMANOIDE;
    }
    @Override
    public String getFormaMoverse() {
        return "Por pasos";
    }
}
@Component
public class RobotKangoro implements IRobotStrategy {
    @Override
    public RobotType getType() {
        return RobotType.KANGORO;
    }
    @Override
    public String getFormaMoverse() {
        return "Salta";
    }
}
@Component
public class RobotRoomba implements IRobotStrategy {

@Override
public RobotType getType() {
return RobotType.ROOMBA;
}

@Override
public String getFormaMoverse() {
return "Se desliza";
}
}

Como puede verse, los Robots son componentes de Spring, esto permitirá que en el proceso de arranque de la aplicación los Beans que genera cada componente lleguen al contenedor de beans y estén disponibles para usarse.

Veamos ahora el RobotContext.

@Service
public class RobotContext {
    @Autowired
    private List<IRobotStrategy> robotStrategies;
    private Map<RobotType,IRobotStrategy> map;
    @PostConstruct
    public void setup() {
        this.map = new HashMap<>();
        robotStrategies.forEach(robotStrategies -> map.put(robotStrategies.getType(), robotStrategies));
    }
    public String getFormaMoverse(RobotType robotType) {
        return this.map.get(robotType).getFormaMoverse();
    }
}

Arriba hemos declarado un la Lista de tipo IRobotStrategy, Spring inyectará en este lista todos los componentes de este tipo, permitiéndonos tener una lista con todos los componentes (o todos los Robots).

El método @PostConstruct, luego de crear los beans nos permitirá crear un mapa asociativo entre el tipo de referencia del Bean (dado en el método getType()) y el Bean en sí.

Ahora el método getFormaMoverse(RobotType), será el que indicando el tipo de Robot llamará al método getFormaMoverse() del Bean respectivo. De esta forma queda culminada nuestra implementación de Strategy. En dependencia del tipo de Robot pasado, se invocará la estrategia adecuada.

Ahora, vamos a crear un controlador, al que se le inyecta el servicio del contexto de Robot, y le invocaremos al método getFormaMoverse indicando el tipo de Robot.

@RestController
@RequestMapping("api/")
public class RobotController {
    @Autowired
    private RobotContext robotContext;
    @GetMapping("robots/formamoverse/{robottype}")
    public ResponseEntity<String> getFormaMoverse(@PathVariable("robottype")RobotType robotType) {
        return new ResponseEntity<String>(this.robotContext.getFormaMoverse(robotType), HttpStatus.OK);
    }
}

Si ejecutamos:

GET http://localhost:8080/api/robots/formamoverse/KANGORO

El resultado será: Salta, y en dependencia del parámetro se invocará la estrategia correcta.

Disclaimer

Es necesario realizar más validaciones, para evitar que si se pasa un tipo que no se corresponde con un Bean no “explote” nuestro programa. No se colocaron las validaciones para que el ejemplo quedara más claro.

Espero te sea útil este artículo, déjame tus comentarios. El código usado está acá.

1 thought on “Patrón: Strategy / GoF

Comments are closed.