Define una familia de algoritmos, encapsula cada uno de ellos y los hace intercambiables. Permite que un algoritmo varié independientemente de los clientes que lo usan.
Define una familia de algoritmos, encapsula cada uno de ellos y los hace intercambiables. Permite que un algoritmo varié independientemente de los clientes que lo usan.
Muchas clases relacionadas difieren solo en su comportamiento. Las estrategias permiten configurar una clase con un determinado comportamiento entre muchos posibles.
Se necesitan distintas variantes de un algoritmo.
Un algoritmo usa daos que los clientes no deberían conocer. Use el patón Strategy para evitar exponer estructuras de datos complejas y dependientes del algoritmo
Una clase define muchos comportamientos y estos se representan como múltiples sentencias condicionales en sus operaciones. En vez de tener muchos condicionales, podemos mover las ramas de estos a sus propias clases estrategia
Estructura del Patrón de comportamiento Estrategia (GOF)
El patrón Strategy tiene sentido cuando nos encontramos en un escenario en el que para conseguir un objetivo, tenemos diferentes formas de afrontarlo. Por poner un ejemplo, imaginemos que estamos desarrollando un programa que añade estilos en formato HTML a un texto. Los estilos que soporta serán: poner en negrita, letra cursiva y subrayar.
Una solución que podríamos dar sería:
public enum Styles { Bold, Italic, Underline } public class Styler { public string SetStyle(string input, Styles style) { switch (style) { case Styles.Bold: return "<b>" + input + "</b>"; case Styles.Italic: return "<i>" + input + "</i>"; case Styles.Underline: return "<u>" + input + "</u>"; default: throw new NotImplementedException("This style is not supported"); } } }
Como podemos ver, hemos usado un bloque de tipo “switch” y en dependencia del estilo que queramos añadirle al texto de entrada (“input”), realizamos una acción u otra. El resultado final es que si dentro de dos días tenemos el requerimiento de añadir dos o tres estilos nuevos a nuestro sistema, tendremos que añadir más bloques “case” a nuestro código. Esto en definitiva, lo que provocaría es que no estuviéramos cumpliendo con el principio SOLID de Open Closed.
Este principio señala que nuestro código debería estar abierto a la extensión, pero cerrado a la modificación. Es decir, que para añadir una nueva funcionalidad, no tengamos la necesidad de modificar los algoritmos que ya están programados. Y es aquí es donde el patrón strategy cobra su importancia.
Con el fin de no tener que modificar nuestro bloque “switch” con cada nuevo estilo que queramos aplicar al texto, vamos a dividir cada uno de los estilos existentes, en clases más pequeñas, que solo resuelvan un estilo cada una:
public class BoldStyler { public string SetStyle(string input) { return "<b>" + input + "</b>"; } } public class ItalicStyler { public string SetStyle(string input) { return "<i>" + input + "</i>"; } } public class UnderlineStyler { public string SetStyle(string input) { return "<u>" + input + "</u>"; } }
Como podemos ver, todas las clases son muy parecidas y podríamos sacar una interface común que defina su comportamiento:
public interface IStyler { string SetStyle(string input); } public class BoldStyler : IStyler //... public class ItalicStyler : IStyler //... public class UnderlineStyler : IStyler //...
Ahora tendríamos que modificar el código inicial para que use estas nuevas clases y seguir resolviendo el problema:
public class Styler { public string SetStyle(string input, IStyler styler) { return styler.SetStyle(input); } }
Y si por un casual, ahora necesitáramos añadir un nuevo formato a nuestro programa, solo tendríamos que desarrollar un nuevo objeto que implementara “IStyler”. Y así evitaríamos tener que modificar el código que ya tenemos escrito.
Dando un repaso a los objetos que hemos ido desarrollando hasta este momento, podríamos extraer un diagrama de clases como este:
Donde vemos que nuestro objeto “Styler” consume objetos que implementan “IStyler” como son “BoldStyler”, “ItalicStyler”, … . Un diagrama semejante al que define el patrón Strategy:
Donde los artefactos son:
En este primer ejemplo hemos creado un contexto que acepta recibir una estrategia como parámetro, pero quizá, el problema podría ser que obligatoriamente hay que aplicar los tres formatos al texto que le pasemos. En este caso podríamos generar una clase de contexto como esta:
public class Styler { private readonly List<IStyler> strategies = new List<IStyler> { new BoldStyler(), new ItalicStyler(), new UnderlineStyler() }; public string SetStyle(string input) { var result = input; foreach(var strategy in this.strategies) { result += strategy.SetStyle(result); } return result; } }
Y si usáramos alguna Framework de inyección de dependencias como Autofac, StructureMaps o NInject, podríamos aprovecharnos de los sistemas de escaneo de ensamblados para poder obtener en el constructor todas las estrategias de un tipo correspondiente:
public class Styler { private readonly IEnumerable<IStyler> strategies; public Styler(IEnumerable<IStyler> strategies) { this.strategies = strategies; } public string SetStyle(string input) { var result = input; foreach(var strategy in this.strategies) { result += strategy.SetStyle(result); } return result; } }
Una de las grandes ventajas del Strategy Pattern es que no es exclusivo de c# o la plataforma .Net, puede ser usado con diferentes lenguajes de programación, como por ejemplo Javascript. Una implementación de este mismo código podría ser esta:
function HtmlStyler() { this.setStyle = function (input) { var result = input; for (var key in this.strategies) { var strategy = this.strategies[key]; if (strategy.setStyle) result = strategy.setStyle(result); else throw "Invalid strategy"; } return result; }; } HtmlStyler.prototype.strategies = { }; HtmlStyler.prototype.strategies.boldStyler = { setStyle: function(input) { return '<b>' + input + '</b>'; }, }; HtmlStyler.prototype.strategies.italicStyler = { setStyle: function (input) { return '<i>' + input + '</i>'; }, }; HtmlStyler.prototype.strategies.underlineStyler = { setStyle: function (input) { return '<u>' + input + '</u>'; }, };
La peculiaridad de esta implentación en Javascript es el uso de “prototype” para facilitar las futuras características nuevas que se pueden desarrollar. No hará falta modificar el programa original, si no añadir una nueva propiedad a las estrategias del prototipo de nuestro objeto.
Y cómo no, también podríamos realizar el mismo código en el lenguaje de programación de moda, Typescript:
interface IStyler { setStyle: (input: string) => string; } class HTMLStyler { strategies: IStyler[]; constructor (strategies: IStyler[]) { this.strategies = strategies; } setStyle(input: string):string { var result: string = input; for (var i = 0; i < this.strategies.length; i++) { var strategy : IStyler = this.strategies[i]; result = strategy.setStyle(result); } return result; } } class BoldStyler implements IStyler { setStyle(input: string) { return '<b>' + input + '</b>'; } } class ItalicStyler implements IStyler { setStyle(input: string) { return '<i>' + input + '</i>'; } } class UnderlineStyler implements IStyler { setStyle(input: string) { return '<u>' + input + '</u>'; } } var styler = new HTMLStyler([new BoldStyler(), new ItalicStyler(), new UnderlineStyler()]); alert(styler.setStyle('hola!'));
Y podremos ver claramente que el resultado final se asemeja más al que desarrollamos con c#, que a la solución propuesta en javascript.
Mientras describíamos el patrón Strategy hemos dejado caer alguno de sus beneficios, como por ejemplo que es más fácil de leer el código. También será más fácil por tanto de mantener y por supuesto de ampliar con nuevas funcionalidades. Gracias a este patrón vamos a cumplir con dos de los principios de SOLID: el principio de responsabilidad única, al crear pequeñas clases que contienen un algoritmo muy concreto; y el de abierto/cerrado, abriendo nuestra solución a la extensión pero no a la modificación.
También queda claro el problema que podría suponer a largo plazo: una gran cantidad nueva de objetos para que sean gestionados por el hilo principal de nuestro programa. Por lo que para un sistema de tiempo real o donde la velocidad de respuesta y el poco consumo de recursos, fueran lo más importante, no sería la implementación ideal.
No obstante, teniendo en cuenta que en este ejemplo usamos lenguajes como Javascript o c#, en los que la velocidad no es su punto fuerte, siempre deberíamos pensar en este patrón antes de escribir un bloque “switch”.
************************************************************************ ***********************************************************************A continuación hay un ejemplo sencillo de este patrón. El ejemplo trata del formateo de una fecha.
Es el que define la interfaz
Public Interface IFormatofechaStrategy Function AplicarFormato( _ ByVal dia As Integer, _ ByVal mes As Integer, _ ByVal año As Integer) As String End Interface
Public Class StrategyContexto Implements IFormatofechaStrategy Private _interfazFormatoFecha As IFormatofechaStrategy Public Property FormatoFecha As IFormatofechaStrategy Get Return _interfazFormatoFecha End Get Set(ByVal value As IFormatofechaStrategy) _interfazFormatoFecha = value End Set End Property Public Function AplicarFormato( _ ByVal dia As Integer, _ ByVal mes As Integer, _ ByVal año As Integer) As String _ Implements IFormatofechaStrategy.AplicarFormato Return FormatoFecha.AplicarFormato(dia, mes, año) End Function End Class
Public Class StrategyConcretaFormatoFechaEuropa Implements IFormatofechaStrategy Public Function AplicarFormato( _ ByVal dia As Integer, _ ByVal mes As Integer, _ ByVal año As Integer) As String _ Implements IFormatofechaStrategy.AplicarFormato Return String.Format("{0,2:00}-{1,2:00}-{2,4:0000}", _ dia, mes, año) End Function End Class
Public Class StrategyConcretaFormatoFechaEEUU Implements IFormatofechaStrategy Public Function AplicarFormato( _ ByVal dia As Integer, _ ByVal mes As Integer, _ ByVal año As Integer) As String _ Implements IFormatofechaStrategy.AplicarFormato Return String.Format("{0,4:0000}-{1,2:00}-{2,2:00}", _ año, mes, dia) End Function End Class
''' <summary> ''' Formulario Cliente que usa el Patron Estrategia ''' </summary> ''' <remarks> ''' Hay dos botones [ButtonEuropa] y [ButtonEEUU] para llamar al patron ''' Hay un TextBox [TextBox1] donde se muestran los resultados '''</remarks> Public Class FormClienteStrategy ''' <summary> ''' Boton (llamar al patron Estrategia) Formato fecha Europea ''' </summary> Private Sub ButtonEuropa_Click( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles ButtonEuropa.Click Dim objContextoStrategy As New StrategyContexto objContextoStrategy.FormatoFecha = New StrategyConcretaFormatoFechaEuropa Me.TextBox1.Text = objContextoStrategy.AplicarFormato( _ Date.Now.Day, Date.Now.Month, Date.Now.Year) End Sub ''' <summary> ''' Boton (llamar al patron Estrategia) Formato fecha EEUU ''' </summary> Private Sub ButtonEEUU_Click( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles ButtonEEUU.Click Dim objContextoStrategy As New StrategyContexto objContextoStrategy.FormatoFecha = New StrategyConcretaFormatoFechaEEUU Me.TextBox1.Text = objContextoStrategy.AplicarFormato( _ Date.Now.Day, Date.Now.Month, Date.Now.Year) End Sub ''' <summary> ''' Boton cerrar formulario ''' </summary> Private Sub ButtonTerminar_Click( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles ButtonTerminar.Click Me.Close() End Sub End Class
Patrones de Diseño
Elementos de software orientado a objetos reutilizable
Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides
Pearson Educación, SA Madrid
ISBN 0-201-63361-2
Patrones de diseño: Strategy
http://programandonet.com/web/patrones-de-diseno-strategy
Fernando Escolar
2012-03-10