El patrón Estrategia (Strategy)

Descripción general

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.

[TOC] Tabla de Contenidos


↑↑↑

El patrón Estrategia (Strategy)


↑↑↑

Propósito

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.


↑↑↑

Aplicabilidad

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 patron Estrategia

Estructura del Patrón de comportamiento Estrategia (GOF)


↑↑↑

Participantes


↑↑↑

Consecuencias


↑↑↑

Un ejemplo

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:

styler-strategy-pattern

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:

strategy-pattern

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.

Conclusiones

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”.

************************************************************************ ***********************************************************************

↑↑↑

Código de ejemplo

A continuación hay un ejemplo sencillo de este patrón. El ejemplo trata del formateo de una fecha.


↑↑↑

Objeto Estrategia

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

↑↑↑

Objeto Contexto

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

↑↑↑

Objeto Estrategia Concreta

Estrategia formato fecha europea

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

Estrategia formato fecha Americana

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

↑↑↑

El Objeto Cliente

''' <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

↑↑↑

Bibliografia

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


↑↑↑

A.2.Enlaces

[Para saber mas]
Wikipedia - Patrón de diseño
Wikipedia - Strategy (patrón de diseño)
Patrones de diseño - La estrategia
[Grupo de documentos]
[Documento Index]
[Documento Start]
[Imprimir el Documento]