Strategy (patrón de diseño)
El patrón Estrategia (Strategy) es un patrón de diseño para el desarrollo de software. Se clasifica como patrón de comportamiento porque determina cómo se debe realizar el intercambio de mensajes entre diferentes objetos para resolver una tarea. El patrón estrategia permite mantener un conjunto de algoritmos de entre los cuales el objeto cliente puede elegir aquel que le conviene e intercambiarlo dinámicamente según sus necesidades.
Motivación
[editar]Suponiendo un editor de textos con diferentes algoritmos para particionar un texto en líneas (justificado, alineado a la izquierda, etc.), se desea separar las clases clientes de los diferentes algoritmos de partición, por diversos motivos:
- Incluir el código de los algoritmos en los clientes hace que estos sean demasiado grandes y complicados de mantener y/o extender.
- El cliente no va a necesitar todos los algoritmos en todos los casos, de modo que no queremos que dicho cliente los almacene si no los va a usar.
- Si existiesen clientes distintos que usasen los mismos algoritmos, habría que duplicar el código, por tanto, esta situación no favorece la reutilización.
La solución que el patrón estrategia supone para este escenario pasa por encapsular los distintos algoritmos en una jerarquía y que el cliente trabaje contra un objeto intermediario contexto. El cliente puede elegir el algoritmo que prefiera de entre los disponibles, o el mismo contexto puede ser el que elija el más apropiado para cada situación.
Aplicabilidad
[editar]Cualquier programa que ofrezca un servicio o función determinada, que pueda ser realizada de varias maneras, es candidato a utilizar el patrón estrategia. Puede haber cualquier número de estrategias y cualquiera de ellas podrá ser intercambiada por otra en cualquier momento, incluso en tiempo de ejecución. Si muchas clases relacionadas se diferencian únicamente por su comportamiento, se crea una superclase que almacene el comportamiento común y que hará de interfaz hacia las clases concretas.
Si un algoritmo utiliza información que no deberían conocer los clientes, la utilización del patrón estrategia evita la exposición de dichas estructuras. Aplicando el patrón a una clase que defina múltiples comportamientos mediante instrucciones condicionales, se evita emplear estas instrucciones, moviendo el código a clases independientes donde se almacenará cada estrategia.
Efectivamente, como se comenta anteriormente, este patrón de diseño nos sirve para intercambiar un sinnúmero de estrategias posibles.
Participantes
[editar]Contexto (Context) : Es el elemento que usa los algoritmos, por tanto, delega en la jerarquía de estrategias. Configura una estrategia concreta mediante una referencia a la estrategia necesaria. Puede definir una interfaz que permita a la estrategia el acceso a sus datos en caso de que fuese necesario el intercambio de información entre el contexto y la estrategia. En caso de no definir dicha interfaz, el contexto podría pasarse a sí mismo a la estrategia como parámetro.
Estrategia (Strategy): Declara una interfaz común para todos los algoritmos soportados. Esta interfaz será usada por el contexto para invocar a la estrategia concreta.
EstrategiaConcreta (ConcreteStrategy): Implementa el algoritmo utilizando la interfaz definida por la estrategia.
Colaboraciones
[editar]Es necesario el intercambio de información entre estrategia y contexto. Este intercambio puede realizarse de dos maneras:
- Mediante parámetros en los métodos de la estrategia.
- Mandándose, el contexto, a sí mismo a la estrategia.
Los clientes del contexto lo configuran con una estrategia concreta. A partir de ahí, solo se interactúa con el contexto.
Consecuencias
[editar]La herencia puede ayudar a factorizar las partes comunes de las familias de algoritmos (sustituyendo el uso de bloques de instrucciones condicionales). En este contexto es común la aparición conjunta de otros patrones como el patrón plantilla.
El uso del patrón proporciona una alternativa a la extensión de contextos, ya que puede realizarse un cambio dinámico de estrategia.
Los clientes deben conocer las diferentes estrategias y debe comprender las posibilidades que ofrecen.
Como contrapartida, aumenta el número de objetos creados, por lo que se produce una penalización en la comunicación entre estrategia y contexto (hay una indirección adicional).
Implementación
[editar]Entre las posibilidades disponibles a la hora de definir la interfaz entre estrategia y contexto, están:
- Pasar como parámetro la información necesaria para la estrategia implica un bajo acoplamiento y la posibilidad de envío de datos innecesarios.
- Pasar como parámetro el contexto y dejar que la estrategia solicite la información que necesita supone un alto acoplamiento entre ellos.
- Mantener en la estrategia una referencia al contexto (similar al anterior).
También puede ocurrir que se creen y se utilicen los objetos estrategia en el contexto solo si es necesario, en tal caso las estrategias serán opcionales.
Ejemplo
[editar]public abstract class EstrategiaDibujo extends JFrame {
private float[] _x,_y;
private Color _c;
private int _ancho,_alto;
public EstrategiaDibujo() {
....
}
public abstract void dibujar(float[] px, float[] py);
}
Lo importante de esta clase es que cada una de las estrategias que diseñemos tendrá que sobrescribir el método dibujar() y proveer un algoritmo concreto para dicha estrategia.
public class EstrategiaDibujoConcreta1 extends EstrategiaDibujo{
...
public void dibujar(float[] px, float[] py){ ... }
...
}
public class EstrategiaDibujoConcreta2 extends EstrategiaDibujo{
...
public void dibujar(float[] px, float[] py){ ... }
...
}
El Contexto es la clase que decide qué estrategia utilizar en cada momento, la decisión se puede realizar mediante algún parámetro que le envía el cliente aunque como hemos dicho puede ser él mismo el que elija la estrategia más adecuada :
public class CreadorDibujos {
private EstrategiaDibujo _estrategia;
private float[] _x,_y;
public CreadorDibujos() {
// Establecer estrategia por defecto.
}
public void establecerDibujoBarras() {
_estrategia = new EstrategiaDibujoConcreta1();
}
public void establecerDibujoLineas() {
_estrategia = new EstrategiaDibujoConcreta2();
}
..............
public void dibuja() {
_estrategia.dibujar(_x,_y);
}
}
Como podemos comprobar el funcionamiento de este patrón es muy simple y el añadir nuevas estrategias a nuestro programa es muy sencillo y apenas implica modificación de código alguna.
Ejemplos en varios lenguajes
[editar]#include <iostream>
using namespace std;
class StrategyInterface
{
public:
virtual void execute() = 0;
};
class ConcreteStrategyA: public StrategyInterface
{
public:
virtual void execute()
{
cout << "Called ConcreteStrategyA execute method" << endl;
}
};
class ConcreteStrategyB: public StrategyInterface
{
public:
virtual void execute()
{
cout << "Called ConcreteStrategyB execute method" << endl;
}
};
class ConcreteStrategyC: public StrategyInterface
{
public:
virtual void execute()
{
cout << "Called ConcreteStrategyC execute method" << endl;
}
};
class Context
{
private:
StrategyInterface *_strategy;
public:
Context(StrategyInterface *strategy):_strategy(strategy)
{
}
void set_strategy(StrategyInterface *strategy)
{
_strategy = strategy;
}
void execute()
{
_strategy->execute();
}
};
int main(int argc, char *argv[])
{
ConcreteStrategyA concreteStrategyA;
ConcreteStrategyB concreteStrategyB;
ConcreteStrategyC concreteStrategyC;
Context contextA(&concreteStrategyA);
Context contextB(&concreteStrategyB);
Context contextC(&concreteStrategyC);
contextA.execute();
contextB.execute();
contextC.execute();
contextA.set_strategy(&concreteStrategyB);
contextA.execute();
contextA.set_strategy(&concreteStrategyC);
contextA.execute();
return 0;
}
public class Main {
public static void main(String args[])
{
//Usamos la estrategia A
Strategy estrategia_inicial = new StrategyA();
Context context = new Context(estrategia_inicial);
context.some_method();
//Decidimos usar la estrategia B
Strategy estrategia2 = new StrategyB();
context.setStrategy(estrategia2);
context.some_method();
//Finalmente,usamos de nuevo la estrategia A
context.setStrategy(estrategia_inicial);
context.some_method();
/** Salida:
* Estrategia A
* Estrategia B
* Estrategia A
**/
}
}
public class Context {
Strategy c;
public Context( Strategy c )
{
this.c = c;
}
public void setStrategy(Strategy c) {
this.c = c;
}
//Método de estrategia 'c'
public void some_method()
{
c.behaviour();
}
}
public Interface Strategy{
public void behaviour();
}
public class StrategyA implements Strategy{
@Override
public void behaviour() {
System.out.println("Estrategia A");
}
}
public class StrategyB implements Strategy{
@Override
public void behaviour() {
System.out.println("Estrategia B");
}
}
Python ya lo implementa internamente y no se puede programar explícitamente: Este es un ejemplo con GUI
class Button:
"""A very basic button widget."""
def __init__(self, submit_func, label):
self.on_submit = submit_func # Poner la funcion estrategia directamente
self.label = label
# Se crean dos objetos con estrategias diferentes
button1 = Button(sum, "Add 'em")
button2 = Button(lambda nums: " ".join(map(str, nums)), "Join 'em")
# Probamos cada boton
numbers = range(1, 10) # Lista del 1 al 9
print button1.on_submit(numbers) # muestra "45"
print button2.on_submit(numbers) # muestra "1 2 3 4 5 6 7 8 9"
En C# 3.0, pueden usarse "expresiones lambda" para hacer lo mismo que en el ejemplo en Python.
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
{
var button1 = new MyButton((x) => x.Sum().ToString(), "Add 'em");
var button2 = new MyButton((x) => string.Join(" ", x.Select(y => y.ToString()).ToArray()), "Join 'em");
var numbers = Enumerable.Range(1, 10);
Console.WriteLine(button1.Submit(numbers));
Console.WriteLine(button2.Submit(numbers));
Console.ReadLine();
}
public class MyButton
{
private readonly Func<IEnumerable<int>, string> submitFunction;
public string Label { get; private set; }
public MyButton(Func<IEnumerable<int>, string> submitFunction, string label)
{
this.submitFunction = submitFunction;
Label = label;
}
public string Submit(IEnumerable<int> data)
{
return submitFunction(data);
}
}
}
using System;
namespace Wikipedia.Patterns.Strategy
{
// MainApp Test para aplicacion
class MainApp
{
static void Main()
{
Context context;
// Tres contextos con diferentes estrategias
context = new Context(new ConcreteStrategyA());
context.Execute();
context = new Context(new ConcreteStrategyB());
context.Execute();
context = new Context(new ConcreteStrategyC());
context.Execute();
}
}
interface IStrategy
{
void Execute();
}
// Implementa el algoritmo usando el patron estrategia
class ConcreteStrategyA : IStrategy
{
public void Execute()
{
Console.WriteLine( "Called ConcreteStrategyA.Execute()" );
}
}
class ConcreteStrategyB : IStrategy
{
public void Execute()
{
Console.WriteLine( "Called ConcreteStrategyB.Execute()" );
}
}
class ConcreteStrategyC : IStrategy
{
public void Execute()
{
Console.WriteLine( "Called ConcreteStrategyC.Execute()" );
}
}
// Contiene un objeto ConcreteStrategy y mantiene una referencia a un objeto Strategy
class Context
{
IStrategy strategy;
// Constructor
public Context(IStrategy strategy)
{
this.strategy = strategy;
}
public void Execute()
{
strategy.Execute();
}
}
}
ActionScript 3
[editar]//invoked from application.initialize
private function init() : void
{
var context:Context;
context = new Context( new ConcreteStrategyA() );
context.execute();
context = new Context( new ConcreteStrategyB() );
context.execute();
context = new Context( new ConcreteStrategyC() );
context.execute();
}
package org.wikipedia.patterns.strategy
{
public interface IStrategy
{
function execute() : void ;
}
}
package org.wikipedia.patterns.strategy
{
public final class ConcreteStrategyA implements IStrategy
{
public function execute():void
{
trace( "ConcreteStrategyA.execute(); invoked" );
}
}
}
package org.wikipedia.patterns.strategy
{
public final class ConcreteStrategyB implements IStrategy
{
public function execute():void
{
trace( "ConcreteStrategyB.execute(); invoked" );
}
}
}
package org.wikipedia.patterns.strategy
{
public final class ConcreteStrategyC implements IStrategy
{
public function execute():void
{
trace( "ConcreteStrategyC.execute(); invoked" );
}
}
}
package org.wikipedia.patterns.strategy
{
public class Context
{
private var strategy:IStrategy;
public function Context(strategy:IStrategy)
{
this.strategy = strategy;
}
public function execute() : void
{
strategy.execute();
}
}
}
<?php
class StrategyExample {
public function __construct() {
$context = new Context(new ConcreteStrategyA());
$context->execute();
$context = new Context(new ConcreteStrategyB());
$context->execute();
$context = new Context(new ConcreteStrategyC());
$context->execute();
}
}
interface IStrategy {
public function execute();
}
class ConcreteStrategyA implements IStrategy {
public function execute() {
echo "Called ConcreteStrategyA execute method\n";
}
}
class ConcreteStrategyB implements IStrategy {
public function execute() {
echo "Called ConcreteStrategyB execute method\n";
}
}
class ConcreteStrategyC implements IStrategy {
public function execute() {
echo "Called ConcreteStrategyC execute method\n";
}
}
class Context {
var $strategy;
public function __construct(IStrategy $strategy) {
$this->strategy = $strategy;
}
public function execute() {
$this->strategy->execute();
}
}
new StrategyExample;
?>
Perl lo tiene ya implementado y no se puede programar explícitamente:
sort { lc($a) cmp lc($b) } @items
El patrón de estrategia puede implementarse formalmente mediante Moose:
package Strategy;
use Moose::Role;
requires 'execute';
package FirstStrategy;
use Moose;
with 'Strategy';
sub execute {
print "Called FirstStrategy->execute()\n";
}
package SecondStrategy;
use Moose;
with 'Strategy';
sub execute {
print "Called SecondStrategy->execute()\n";
}
package ThirdStrategy;
use Moose;
with 'Strategy';
sub execute {
print "Called ThirdStrategy->execute()\n";
}
package Context;
use Moose;
has 'strategy' => (
is => 'rw',
does => 'Strategy',
handles => [ 'execute' ], # automatic delegation
);
package StrategyExample;
use Moose;
# Moose's constructor
sub BUILD {
my $context;
$context = Context->new(strategy => 'FirstStrategy');
$context->execute;
$context = Context->new(strategy => 'SecondStrategy');
$context->execute;
$context = Context->new(strategy => 'ThirdStrategy');
$context->execute;
}
package main;
StrategyExample->new;
Enlaces externos
[editar]- Patrón Strategy: explicación y diseño en UML
- Strategy Pattern for Java article Archivado el 5 de noviembre de 2013 en Wayback Machine.
- Strategy Pattern for CSharp article
- Strategy pattern in UML and in LePUS3 (a formal modelling notation)
- Refactoring: Replace Type Code with State/Strategy
- Como modificar el objeto del contexto pasando parámetros con el patrón estrategia por Juan Camba