En la pro­gra­ma­ción orientada a objetos, los design patterns, o patrones de diseño, asisten los de­sa­rro­lla­do­res con es­tra­te­gias y pla­n­ti­llas de so­lu­cio­nes cuya eficacia ha sido probada. Una vez en­co­n­tra­do el esquema de re­so­lu­ción apropiado, todo lo que queda es hacer ajustes in­di­vi­dua­les. Ac­tua­l­me­n­te, hay un total de 70 patrones de diseño adaptados a áreas es­pe­cí­fi­cas de apli­ca­ción. Los patrones strategy se centran en el co­m­po­r­ta­mie­n­to del software.

¿En qué consiste el strategy pattern?

El strategy pattern pertenece a los llamados beha­viou­ral patterns o patrones de co­m­po­r­ta­mie­n­to, que equipan un software con di­fe­re­n­tes métodos de re­so­lu­ción. Estas es­tra­te­gias consisten en una familia de al­go­ri­t­mos que están separados del programa real y son autónomos, es decir in­te­r­ca­m­bia­bles. Un strategy pattern también incluye algunas pautas y ayudas para los de­sa­rro­lla­do­res. Por ejemplo, los strategy patterns describen cómo construir u organizar un grupo de clases y crear objetos. Una ca­ra­c­te­rí­s­ti­ca especial del patrón strategy es que un co­m­po­r­ta­mie­n­to variable de programas y objetos también se puede realizar en el tiempo de ejecución de un software.

¿Cómo es la re­pre­se­n­ta­ción UML de un strategy pattern?

Los strategy patterns se diseñan ge­ne­ra­l­me­n­te con el lenguaje de modelado gráfico UML (Unified Modeling Language). Sirven para vi­sua­li­zar los patrones de diseño con una notación es­ta­n­da­ri­za­da y utilizan ca­ra­c­te­res y símbolos es­pe­cia­les. El UML establece distintos tipos de diagramas para la pro­gra­ma­ción orientada a objetos. Para re­pre­se­n­tar un strategy pattern, se suelen utilizar los llamados diagramas de clase con al menos tres co­m­po­ne­n­tes básicos:

  • Context (contexto o clase de contexto)
  • Strategy (es­tra­te­gia o clase de es­tra­te­gia)
  • Co­n­cre­te­S­tra­te­gy (es­tra­te­gia concreta)

Con el strategy pattern, los co­m­po­ne­n­tes básicos asumen funciones es­pe­cia­les. Los patrones de co­m­po­r­ta­mie­n­to de la clase Context se almacenan en di­fe­re­n­tes clases Strategy. Estas clases separadas contienen los al­go­ri­t­mos llamados Co­n­cre­te­S­tra­te­gies. Uti­li­za­n­do una re­fe­re­n­cia interna, el contexto puede acceder a las variables de co­mpu­tación ex­te­r­na­li­za­das (Co­n­cre­te­S­tra­te­g­yA, Co­n­cre­te­S­tra­te­g­yB, etc.) si lo necesita. No in­ter­ac­túa di­re­c­ta­me­n­te con los al­go­ri­t­mos, sino con una interfaz.

La interfaz Strategy encapsula las variantes de cálculo y puede ser im­ple­me­n­ta­da si­mu­l­tá­nea­me­n­te por todos los al­go­ri­t­mos. Para la in­ter­ac­ción con Context, la interfaz genérica pro­po­r­cio­na solo un método para activar los al­go­ri­t­mos de Co­n­cre­te­S­tra­te­gy. La in­ter­ac­ción con Context incluye, además del acceso a Strategy, in­te­r­ca­m­bio de datos. La interfaz Strategy también participa en los cambios de es­tra­te­gia, que pueden tener lugar, además, en el tiempo de ejecución del programa.

Hecho

La en­ca­p­su­la­ción impide el acceso directo a los al­go­ri­t­mos y a es­tru­c­tu­ras de datos internos. Una instancia externa (Client, Context) solo puede acceder a los cálculos y funciones a través de in­te­r­fa­ces definidas y, además, solo puede acceder a los métodos y elementos de datos de un objeto que sean re­le­va­n­tes para ella.

A co­n­ti­nua­ción, ex­pli­ca­re­mos cómo se aplica un patrón de diseño en un proyecto práctico uti­li­za­n­do un ejemplo de strategy pattern.

Ejemplo de un strategy pattern

En nuestro ejemplo nos orie­n­ta­mos según el proyecto de estudio sobre strategy patterns de Philipp Hauer. En el mismo vamos a crear una apli­ca­ción de na­ve­ga­ción que hace uso de un strategy pattern. La apli­ca­ción debe calcular una ruta basada en los medios de tra­n­s­po­r­te ha­bi­tua­les. El usuario puede elegir entre tres opciones:

  • Peatón (Co­n­cre­te­S­tra­te­g­yA)
  • Coche (Co­n­cre­te­S­tra­te­g­yB)
  • Tra­n­s­po­r­te público (Co­n­cre­te­S­tra­te­g­yC)

Si se tra­n­s­fie­ren estas es­pe­ci­fi­ca­cio­nes a un gráfico UML, se puede observar la es­tru­c­tu­ra y la función del strategy pattern:

En nuestro ejemplo, el cliente o Client es la Interfaz Gráfica de Usuario (GUI) de una apli­ca­ción de na­ve­ga­ción con botones para el cálculo de la ruta. Si el usuario hace una selección y pulsa un botón, se calcula una ruta concreta. El Context (clase Navigator) tiene la tarea de calcular y mostrar una serie de puntos de control en el mapa. La clase Navigator tiene un método para cambiar la es­tra­te­gia de en­ru­ta­mie­n­to activa. Los botones permiten cambiar fá­ci­l­me­n­te entre los medios de tra­n­s­po­r­te.

Si, por ejemplo, se activa un comando co­rre­s­po­n­die­n­te con el botón peatonal del Client, se solicita el servicio “Calcular la ruta peatonal” (Co­n­cre­te­S­tra­te­g­yA). El método exe­cu­teA­l­go­ri­thm() (en nuestro ejemplo, el método: ca­l­cu­la­Ru­ta(A, B)) acepta un origen y un destino y devuelve un conjunto de los puntos de control de la ruta. Context acepta el comando del Client y decide la es­tra­te­gia apropiada basándose en las di­re­c­ti­vas pre­via­me­n­te definidas (policy) (setS­tra­te­gy: peatonal). Mediante Call, delega la solicitud al objeto Strategy y a su interfaz.

Con getS­tra­te­gy(), la es­tra­te­gia ac­tua­l­me­n­te se­le­c­cio­na­da se almacena en Context (clase Navigator). Los re­su­l­ta­dos de los cálculos de Co­n­cre­te­S­tra­te­gy se utilizan para el pro­ce­sa­mie­n­to posterior y en la vi­sua­li­za­ción gráfica de la ruta en la apli­ca­ción de na­ve­ga­ción. Si el usuario elige una ruta diferente, por ejemplo, haciendo clic en el botón “Coche”, Context cambia a la es­tra­te­gia so­li­ci­ta­da (Co­n­cre­te­S­tra­te­g­yB) e inicia un nuevo cálculo a través de otra llamada. Al final del pro­ce­di­mie­n­to, se devuelve una de­s­cri­p­ción de ruta mo­di­fi­ca­da para el coche.

En nuestro ejemplo, la mecánica de patrones puede im­ple­me­n­tar­se con un código re­la­ti­va­me­n­te claro:

Context:

public class Context {
    // Valor por defecto (comportamiento por defecto): ConcreteStrategyA
    private Strategy strategy = new ConcreteStrategyA(); 
    public void execute() { 
        //delega el comportamiento a un objeto de estrategia
        strategy.executeAlgorithm(); 
    }
    public void setStrategy(Strategy strategy) {
        strategy = strategy;
    }
    public Strategy getStrategy() { 
        return strategy; 
    } 
}

Strategy, Co­n­cre­te­S­tra­te­g­yA, Co­n­cre­te­S­tra­te­g­yB:

interface Strategy { 
    public void executeAlgorithm(); 
} 
class ConcreteStrategyA implements Strategy { 
    public void executeAlgorithm() { 
        System.out.println("Concrete Strategy A"); 
    } 
} 
class ConcreteStrategyB implements Strategy { 
    public void executeAlgorithm() { 
        System.out.println("Concrete Strategy B"); 
    } 
}

Client:

public class Client { 
    public static void main(String[] args) { 
        //Comportamiento por defecto 
        Context context = new Context(); 
        context.execute(); 
        //Cambiar el comportamiento 
        context.setStrategy(new ConcreteStrategyB()); 
        context.execute(); 
    } 
}

¿Cuáles son las ventajas y e in­co­n­ve­nie­n­tes del strategy pattern?

Las ventajas de usar un strategy pattern son más evidentes desde la pe­r­s­pe­c­ti­va de un pro­gra­ma­dor y ad­mi­ni­s­tra­dor de sistemas. El desglose en módulos y clases autónomas ayuda a es­tru­c­tu­rar mejor el código del programa. Puesto que los módulos estén de­li­mi­ta­dos en nuestra apli­ca­ción de ejemplo, la tarea del pro­gra­ma­dor será más sencilla. Así pues, se puede limitar el alcance de la clase Navigator mediante la ex­te­r­na­li­za­ción de las es­tra­te­gias y se puede pre­s­ci­n­dir de la creación de subclases en Context.

Las de­pe­n­de­n­cias internas de los segmentos se mantienen dentro de los límites de un código más reducido y cla­ra­me­n­te definido. Por ello, los cambios tienen menos efecto, es decir, no suelen conllevar más cambios (que requieren mucho tiempo) en la pro­gra­ma­ción. En algunos casos, los cambios derivados incluso pueden de­s­ca­r­tar­se por completo. Los segmentos de código más claros también pueden ma­n­te­ne­r­se mejor a largo plazo y el dia­g­nó­s­ti­co y la re­so­lu­ción de problemas se facilitan.

Asimismo, el manejo se hace más sencillo, ya que la apli­ca­ción de ejemplo se puede equipar con una interfaz fácil de usar. Los usuarios pueden usar los botones para controlar el co­m­po­r­ta­mie­n­to del programa (cálculo de la ruta) de forma variable y elegir entre las opciones de forma sencilla.

Puesto que el Context de la apli­ca­ción de na­ve­ga­ción solo in­ter­ac­túa con una interfaz que encapsula los al­go­ri­t­mos, es in­de­pe­n­die­n­te de la apli­ca­ción concreta de los al­go­ri­t­mos in­di­vi­dua­les. Por lo tanto, si se modifican los al­go­ri­t­mos o se in­tro­du­cen nuevas es­tra­te­gias po­s­te­rio­r­me­n­te, no es necesario cambiar el código de Context. Esto permite ampliar las funciones de cálculo de ruta con es­tra­te­gias concretas adi­cio­na­les para rutas en avión, tra­n­s­po­r­te marítimo y tráfico de larga di­s­ta­n­cias. Las nuevas es­tra­te­gias solo tienen que im­ple­me­n­tar la interfaz de Strategy co­rre­c­ta­me­n­te.

Los strategy patterns si­m­pli­fi­can la difícil pro­gra­ma­ción del software orientado a objetos gracias a otra de sus virtudes: permiten el diseño de software re­uti­li­za­ble (módulos) que se puede im­ple­me­n­tar re­pe­ti­da­me­n­te y cuyo de­sa­rro­llo se considera pa­r­ti­cu­la­r­me­n­te difícil. Esto significa que las clases Context re­la­cio­na­das también podrían utilizar las es­tra­te­gias ex­te­r­na­li­za­das para calcular las rutas a través de la interfaz y ya no tendrían que apli­car­las por sí mismas.

A pesar de sus muchas ventajas, el strategy pattern también tiene algunos in­co­n­ve­nie­n­tes. Debido a su es­tru­c­tu­ra más compleja, el diseño del software puede crear re­du­n­da­n­cias e in­e­fi­cie­n­cias en la co­mu­ni­ca­ción interna. Por ejemplo, la interfaz strategy genérica, que todos los al­go­ri­t­mos deben aplicar por igual, a veces puede acabar so­bre­di­me­n­sio­na­da.

Un ejemplo: una vez que Context ha creado e ini­cia­li­za­do ciertos pa­rá­me­tros, los pasa a la interfaz genérica y al método definido en esta. Sin embargo, la es­tra­te­gia que se aplique en última instancia no necesita ne­ce­sa­ria­me­n­te todos los pa­rá­me­tros co­mu­ni­ca­dos de Context y, por lo tanto, no los procesa. Así, la interfaz pro­po­r­cio­na­da no siempre se utiliza de manera óptima en el strategy pattern y a veces se producen tra­n­s­fe­re­n­cias de datos in­ne­ce­sa­rias, con el co­n­si­guie­n­te esfuerzo de co­mu­ni­ca­ción.

En la apli­ca­ción, también existe una estrecha de­pe­n­de­n­cia interna entre el cliente y las es­tra­te­gias. Client hace la selección y solicita la es­tra­te­gia concreta mediante el comando de ac­ti­va­ción (en nuestro ejemplo, el cálculo de la ruta a pie), por lo que debe conocer las Co­n­cre­te­S­tra­te­gies. Por lo tanto, el patrón de diseño strategy solo debe uti­li­zar­se si los cambios de es­tra­te­gia y co­m­po­r­ta­mie­n­to son im­po­r­ta­n­tes para el uso y la fu­n­cio­na­li­dad de un software.

Na­tu­ra­l­me­n­te, los in­co­n­ve­nie­n­tes me­n­cio­na­dos se pueden compensar pa­r­cia­l­me­n­te o minimizar. Por ejemplo, el número de in­s­ta­n­cias de objetos que pueden pro­du­ci­r­se en grandes ca­n­ti­da­des en el strategy pattern puede reducirse si se aplican a un flyweight pattern. La medida también tiene un efecto positivo en los re­qui­si­tos de efi­cie­n­cia y en la memoria de la apli­ca­ción.

¿Dónde se utiliza el patrón strategy?

Como patrón de diseño básico en el de­sa­rro­llo de software, el strategy pattern no está limitado a un solo ámbito de apli­ca­ción. Lo más im­po­r­ta­n­te a la hora de escoger este patrón de diseño es la na­tu­ra­le­za del problema que se quiera resolver. El patrón strategy es ideal para software que ha de resolver tareas y problemas variables, opciones de co­m­po­r­ta­mie­n­to y cambios.

Por ejemplo, los programas que ofrecen di­fe­re­n­tes formatos de al­ma­ce­na­mie­n­to de archivos o varias funciones de cla­si­fi­ca­ción y búsqueda utilizan el strategy pattern. En el ámbito de la co­m­pre­sión de datos también se utilizan programas que emplean diversos al­go­ri­t­mos de co­m­pre­sión basados en este patrón de diseño. Esto permite, por ejemplo, convertir de forma versátil los vídeos a un formato de archivo que ahorre espacio o restaurar archivos co­m­pri­mi­dos (por ejemplo, archivos ZIP o RAR) a su estado original mediante es­tra­te­gias es­pe­cia­les de de­s­co­m­pre­sión. Otro ejemplo es el al­ma­ce­na­mie­n­to de un documento o archivo gráfico en di­fe­re­n­tes formatos.

El patrón de diseño strategy también se utiliza en el de­sa­rro­llo e im­ple­me­n­ta­ción de software de juegos, que, por ejemplo, tienen que reac­cio­nar con fle­xi­bi­li­dad a las si­tua­cio­nes ca­m­bia­n­tes del juego durante el tiempo de ejecución. Los di­fe­re­n­tes pe­r­so­na­jes, los equipos es­pe­cia­les, los patrones de co­m­po­r­ta­mie­n­to de los pe­r­so­na­jes o sus mo­vi­mie­n­tos es­pe­cia­les pueden al­ma­ce­nar­se en forma de Co­n­cre­te­S­tra­te­gies.

Otra área de apli­ca­ción de los strategy patterns es el software de control. Mediante el in­te­r­ca­m­bio de Co­n­cre­te­S­tra­te­gies, los segmentos de cálculo pueden adaptarse fá­ci­l­me­n­te a grupos ocu­pa­cio­na­les, países y regiones. También los programas que traducen los datos a di­fe­re­n­tes formatos gráficos (por ejemplo, como gráficos de líneas, ci­r­cu­la­res o de barras) utilizan strategy patterns.

Se pueden encontrar apli­ca­cio­nes más es­pe­cí­fi­cas de los strategy patterns en la bi­blio­te­ca estándar de Java (Java API) y en los conjuntos de he­rra­mie­n­tas de la interfaz gráfica de Java (por ejemplo, AWT, Swing y SWT), que utilizan un gestor de diseño en el de­sa­rro­llo y la ge­ne­ra­ción de in­te­r­fa­ces gráficas de usuario. Este gestor puede aplicar di­fe­re­n­tes es­tra­te­gias para la di­s­po­si­ción de los co­m­po­ne­n­tes durante el de­sa­rro­llo de la interfaz. El strategy pattern se utiliza también en sistemas de base de datos, co­n­tro­la­do­res de di­s­po­si­ti­vos y programas de se­r­vi­do­res.

Resumen de las funciones más im­po­r­ta­n­tes del strategy pattern

Entre la amplia variedad de los patrones de diseño, el strategy pattern se distingue por las si­guie­n­tes pro­pie­da­des:

  • Está orientado al co­m­po­r­ta­mie­n­to (los co­m­po­r­ta­mie­n­tos y los cambios son más fáciles de programar e im­ple­me­n­tar, los cambios también son posibles durante el tiempo de ejecución de un programa).
  • Está orientado a la efi­cie­n­cia (la ex­te­r­na­li­za­ción si­m­pli­fi­ca y optimiza el código y su ma­n­te­ni­mie­n­to).
  • Está orientado al futuro (los cambios y op­ti­mi­za­cio­nes también son fáciles de realizar a medio y largo plazo).
  • Tiene como objetivo la capacidad de expansión (fa­vo­re­ci­da por el diseño modular y la in­de­pe­n­de­n­cia de los objetos y las clases).
  • Tiene como objetivo la re­uti­li­za­ción (por ejemplo, el uso re­cu­rre­n­te de es­tra­te­gias).
  • Tiene como objetivo optimizar la usa­bi­li­dad, co­n­tro­la­bi­li­dad y co­n­fi­gu­ra­bi­li­dad del software.
  • Requiere co­n­si­de­ra­cio­nes co­n­ce­p­tua­les mi­nu­cio­sas (¿qué se puede ex­te­r­na­li­zar a las clases Strategy, dónde y cómo?).
En resumen

Los strategy patterns permiten el de­sa­rro­llo eficiente y económico de software en la pro­gra­ma­ción orientada a objetos con so­lu­cio­nes de problemas a medida. Ya en la fase de diseño, se puede preparar el proyecto para futuros cambios y mejoras. El sistema, que está orientado a la va­ria­bi­li­dad y al dinamismo, puede así, en general, co­n­tro­lar­se y vigilarse mejor. Los errores e in­co­n­si­s­te­n­cias son más fáciles de eliminar. Los co­m­po­ne­n­tes re­uti­li­za­bles e in­te­r­ca­m­bia­bles ahorran costes de de­sa­rro­llo, es­pe­cia­l­me­n­te en proyectos complejos con una pe­r­s­pe­c­ti­va a largo plazo. Sin embargo, es im­po­r­ta­n­te encontrar el equi­li­brio adecuado: no es raro que los patrones de diseño se usen demasiado o demasiado poco.

Ir al menú principal