Strategy (padrón de deseño)

O padrón Estratexia (Strategy) é un padrón de deseño, clasificado como de comportamento de obxectos. O seu obxectivo é a definición dunha familia de algoritmos, encapsulándoos e facéndoos intercambiables. Deste xeito, ditos algoritmos poderán variar de forma independente ás clases que os usan e ser reutilizados.

Motivación

[editar | editar a fonte]

Imaxinemos o caso da división dun texto en liñas, para o cal existen múltiples algoritmos. Se optásemos por un enfoque tradicional, codificando estes en cada clase cliente que os usase:

  • O código dos clientes sería máis complexo e grande, e resultarían moito máis difíciles de manter, sobre todo no caso de que se permitisen diferentes algoritmos en cada cliente.
  • Pode que non sexan necesarios tódolos algoritmos en tódolos casos, senón que cada un será axeitado nun momento concreto, polo tanto non ten sentido almacenalos todos se non van ser usados.
  • Os cambios nalgún dos algoritmos existentes ou a introdución dun novo será complicada, xa que estes forman parte das clases cliente.

A alternativa que o padrón Estratexia propón a esta situación é a definición de clases que encapsulen os diferentes algoritmos de partición de liñas. Desta forma, os clientes que necesiten dividir un texto non deberán implementar o código de cada algoritmo, pois este estará encapsulado nunha clase estratexia na que o cliente, a través dun contexto intermedio, delegará a responsabilidade de dividir o texto.

Aplicabilidade

[editar | editar a fonte]

É axeitado o uso do padrón Estratexia nas seguintes situacións:

  • Cando existen moitas clases relacionadas que unicamente se diferencian nun comportamento. Mediante a encapsulación das estratexias poderemos configurar unha clase para que tome un comportamento determinado de entre os posibles.
  • Se no sistema é necesario dispoñer de diferentes variantes dun algoritmo. Cada unha delas pode ser definida como unha estratexia concreta.
  • Os clientes non deberían coñecer detalles de implementación dun algoritmo, ou a información que este emprega. A encapsulación do algoritmo nunha estratexia evitará que sexan expostas as estruturas de datos que dependen del.
  • Nunha clase se definen múltiples comportamentos a través de instrucións condicionais. De forma similar ó que sucede no padrón Estado, facendo de cada un destes comportamentos unha estratexia, podemos eliminar do cliente a responsabilidade de decidir que facer.
  • Cando temos unha serie de comportamentos (algoritmos) comúns a varias clases. O uso do padrón Estratexia permitirá encapsular estes algoritmos e facer que poidan ser empregados por diferentes clases, sen repetir a súa implementación en cada unha delas.

Estrutura

[editar | editar a fonte]

Estrutura do padrón Estratexia. Fonte: Design Patterns: Elements of Reusable Object-Oriented Software

Participantes

[editar | editar a fonte]
  • Estratexia: declara unha interface para os algoritmos permitidos, que será empregada polo Contexto para chamar ó implementado por unha EstratexiaConcreta.
  • EstratexiaConcreta: implementa un algoritmo, facendo uso da interface Estratexia.
  • Contexto: é a clase que fai uso dos algoritmos. Está configurado cunha EstratexiaConcreta, da cal garda unha referencia, e que pode cambiar de forma dinámica (en tempo de execución). O contexto pode proporcionar métodos que permitan á estratexia o acceso ós datos seus que necesite para a execución do algoritmo.

Colaboracións

[editar | editar a fonte]

No escenario definido por este padrón atopamos dúas colaboracións principais entre as clases participantes. A primeira delas é a interacción entre Contexto e Estratexia para facer chegar a esta a información necesaria para a execución do algoritmo. Neste punto, atopamos dúas posibles alternativas:

  • O Contexto pasa a información necesaria á Estratexia a través dos parámetros dos seus métodos cada vez que se chama o algoritmo.
  • O Contexto pásase a si mesmo a Estratexia, como sucedía no padrón Estado, e é responsabilidade desta pedir os datos que necesite.

Estas opcións serán analizadas con máis detalle no apartado de Implementación. A segunda colaboración fai referencia ós clientes. Estes, inicialmente, crearán a EstratexiaConcreta coa que configurarán o Contexto (pode facerse a través dalgún padrón de creación), e, despois disto, unicamente terán que interactuar con el.

Consecuencias

[editar | editar a fonte]

A aplicación do padrón Estratexia proporciona as seguintes vantaxes:

  • A través da herdanza na xerarquía de estratexias podemos abstraer as partes comúns dunha familia de algoritmos.
  • O uso deste padrón fronte á extensión de contextos (definir cada posible algoritmo nunha especialización da clase Contexto), non só nos facilita a lexibilidade e mantenilibidade destes; ademais permite que os cambios de estratexia sexan dinámicos (en tempo de execución).
  • Elimina as instrucións condicionais para a selección do comportamento desexado en cada execución do algoritmo, xa que unha vez o Contexto sexa configurado coa estratexia desexada, unicamente será necesario facer unha chamada ó método declarado na interface.
  • Ademais de definir diferentes comportamentos, as estratexias tamén poden proporcionar diferentes implementacións dun mesmo comportamento, permitindo que o cliente decida cal empregar segundo diversos criterios.

Da mesma forma, podemos atopar os seguintes inconvenientes á hora de de empregar este padrón:

  • Os clientes necesitan coñecer as diferentes estratexias para configurar o Contexto coa adecuada, polo tanto o padrón Estratexia só debería usarse cando a elección dunha ou outra sexa, en certa medida, relevante para eles.
  • A comunicación entre o Contexto e a Estratexia pode resultar custosa. Se o paso de información é realizado a través de parámetros no método da Estratexia, podémonos atopar que as diferentes estratexias concretas non empregan os mesmos datos, polo que moitos dos datos enviados polo Contexto non serán usados. Por outra parte, se se pasa o propio Contexto como parámetro, a penalización virá da comunicación bidireccional que se establecerá entre este e a Estratexia para o intercambio de información, posto que o acoplamento entre ambos provoca que a estratexia sexa moito menos reutilizable.
  • Aumenta o número de obxectos no sistema. Isto poderá paliarse coa implementación das estratexias como Singleton, naqueles casos no que estas non almacenen estado interno, operando sempre sobre datos recibidos como parámetro. Pódese ver isto no exemplo de implementación.

Implementación

[editar | editar a fonte]

Como xa se comentou, na definición da interface entre a estratexia e o contexto, existen diferentes alternativas:

  • A información necesaria para a estratexia é pasada como parámetro. No apartado de Consecuencias presentouse o inconveniente disto: as estratexias poden non necesitar todas a mesma información e polo tanto o Contexto podería pasarlles datos que non van usar.
  • O Contexto pásase a si mesmo como parámetro, e é a Estratexia a encargada de pedir explicitamente os datos que necesite. Isto supón un maior acoplamento entre Estratexia e Contexto, xa que este último terá que definir unha interface máis elaborada para os seus datos, e a Estratexia deberá coñecela.
  • Unha solución similar á anterior sería que o Contexto só sexa pasado unha vez á Estratexia, e esta garde unha referencia a el, aínda que os inconvenientes serían os mesmos.

O uso dunha ou outra opción estará determinado polo algoritmo concreto e as necesidades do sistema.

Outra cuestión de implementación a tratar é permitir o emprego opcional das estratexias. Deste xeito os clientes poderían non coñecer as estratexias concretas cando non fose necesario. O Contexto simplemente tería que comprobar se ten un obxecto estratexia asociado e, se non é así, executar un comportamento por defecto. Este comportamento podería ser implementado incluso como unha estratexia, eliminando así a comprobación no Contexto (verase no exemplo de implementación).

Exemplo de implementación

[editar | editar a fonte]

A continuación preséntase un exemplo de implementación do padrón estratexia para o problema da ordenación dun vector. En relación co comentado anteriormente, unha posible optimización neste caso sería o uso do padrón Singleton para os obxectos estratexia, xa que estes son completamente independientes do Contexto e soamente coa información pasada como parámetro (o vector e o tamaño deste) poden realizar a ordenación; é dicir non almacenan estado interno referente ó contexto. Evidentemente, a vantaxe disto sería a redución do número de obxectos no sistema ante o uso das estratexias por parte de diferentes obxectos Contexto.

public class Cliente {
	
	public static void main(String argv[]) {
		Contexto c = new Contexto(7);

		System.out.println("Sen Ordenamento: " + c.visualizarVector());

		c.establecerOrdenamento(new OrdenamentoSeleccion());
		System.out.println("Ordenamento Burbulla: " + c.visualizarVector());
	}
}
public class Contexto {
	
	private int _vector[];
	private int _tamano;
	private Ordenamento _ordenamento;

	public Contexto(int tamano) {
		_tamano = tamano;
		_vector = new int[_tamano];
		_ordenamento = new SenOrdenamento();
		inicializar();
	}

	// Este método inicializa _vector con valores no rango (-100,100)
	public void inicializar() {
		java.util.Random generador = new java.util.Random();

		for (int i = 0; i < _tamano; i++)
			_vector[i] = generador.nextInt() % 100;
	}

	// Este método permítenos cambiar a estratexia de ordenamento
	public void establecerOrdenamento(Ordenamento o) {
		_ordenamento = o;
	}

	// Úsase a estratexia para ordenar o vector antes de convertilo nun String
	public String visualizarVector() {
		_ordenamento.ordenar(_vector, _tamano);
		int i;
		String s = "";
		for (i = 0; i < _tamano; i++) {
			s += _vector[i] + " ";
		}
		return s;
	}
}
public abstract class Ordenamento {
	public abstract void ordenar(int vector[], int tamano);
}
public class OrdenamentoBurbulla extends Ordenamento {
	
	public void ordenar(int vector[], int tamano) {
		int i, j, temp;
		for (i = 1; i < tamano; i++)
			for (j = 0; j < tamano - 1; j++)
				if (vector[j] > vector[j + 1]) {
					temp = vector[j];
					vector[j] = vector[j + 1];
					vector[j + 1] = temp;
				}
	}
}
public class OrdenamentoInsercion extends Ordenamento {
	
	public void ordenar(int vector[], int tamano) {
		int i, temp, j;
		for (i = 1; i < tamano; i++) {
			temp = vector[i];
			for (j = i - 1; (j >= 0) && (vector[j] > temp); j--)
				vector[j + 1] = vector[j];
			vector[j + 1] = temp;
		}
	}
}
public class OrdenamentoSeleccion extends Ordenamento {
	
	public void ordenar(int vector[], int tamano) {
		int i, j, p, temp;
		int lim = tamano - 1;
		for (i = 0; i < (tamano - 1); i++) {
			p = i;
			for (j = i + 1; j <= lim; j++) {
				if (vector[j] < vector[p])
					p = j;
			}
			temp = vector[p];
			vector[p] = vector[i];
			vector[i] = temp;

		}
	}
}
public class SenOrdenamento extends Ordenamento {
	
	public void ordenar(int vector[], int tamano) {
		// Método baleiro. O vector queda como estaba.
	}
}

Diagrama de clases

[editar | editar a fonte]

Diagrama de clases correspondente ó exemplo de implementación.

Diagrama de secuencia

[editar | editar a fonte]

Véxase tamén

[editar | editar a fonte]

Bibliografía

[editar | editar a fonte]
  • Design Patterns. Elements of Reusable Object-Oriented Software - Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides - Addison Wesley (GoF - Gang of Four)