el patrón estrategia de diseño de software

24
El patrón Estrategia Fecha de la última revisión: 21.05.2014 Revisión 3.0 (21.05.2014) Miguel Ángel Abián 1. Introducción En este artículo se expone el patrón Estrategia desde dos puntos de vista: el teórico y el práctico. Por un lado, se explican los argumentos teóricos para usar este patrón; por otro, se dan algunos consejos para saber cuándo conviene usarlo y se exponen tres ejemplos completos de su uso en Java. Para seguir el artículo, no se necesita ningún conocimiento previo que no sea comprender, aun someramente, el lenguaje Java. Según la última versión del diccionario de la Real Academia Española, un patrón es "el modelo que sirve de muestra para sacar otra cosa igual". Aunque la definición no usa ningún término informático, se adecua bastante bien al sentido con que se emplea patrón en la ingeniería del software. En esta disciplina, los patrones no son muestras físicas o ejemplares a partir de los cuales se puedan construir cosas iguales; sino representaciones abstractas que sirven para especificar y documentar soluciones recurrentes a ciertos problemas habituales. Pese a lo mucho que se usan los patrones de software, no abundan las definiciones del término "patrón de software". Más aún: incluso el creador del término patrón en el sentido de buena práctica (aplicado a la arquitectura, no a la construcción de software) parecía sentirse más cómodo escribiendo un libro voluminoso, abstracto y, a ratos, confuso que definiendo de forma clara y precisa el concepto de patrón. Una definición que no hará mal a nadie es la que aparece en [1]: [Un patrón] es una descripción en un formato fijo de cómo resolver un cierto tipo de problemas. Según Brad Appleton (la definición la leí hace cuatro o cinco años en http://www.enteract.com/~bradapp/docs/pattern-intro.html; la dirección ya no funciona), un patrón es [...] una unidad de información instructiva con nombre, la cual captura la estructura esencial y la compresión de una familia de soluciones exitosas probadas para un problema recurrente que ocurre dentro de un cierto contexto y de un sistema de fuerzas. La definición más completa que conozco para el término aparece en [2] y es obra de James Coplien: Un patrón es una regla constituida por tres partes; la cual expresa una relación entre un cierto contexto, un cierto sistema de fuerzas que ocurren repetidamente en ese contexto y una cierta configuración de software que permite a estas fuerzas resolverse a sí mismas. Según esta definición, tres son las partes fundamentales de un patrón: un sistema de fuerzas, un contexto y una configuración de software. Sistema de fuerzas es el término que usa Coplien como generalización del tipo de criterios que usan los ingenieros de software y los programadores para argumentar sus diseños e implementaciones. Cada vez que se diseña o implementa un componente de software, las fuerzas son los objetivos y restricciones que deben considerar ingenieros y programadores para el componente. Hallar una solución de software para un problema es solucionar el sistema de fuerzas asociado al problema. Una fuerza puede ser la velocidad de ejecución, el consumo de memoria RAM, la interoperabilidad con otros programas, la existencia de errores, etc. La expresión configuración de software es usada por Coplien para denotar cualquier conjunto de reglas de diseño susceptible de ser utilizado para solucionar un sistema de fuerzas. Solucionar un sistema de fuerzas es encontrar un equilibrio óptimo entre las fuerzas que afectan al sistema. Dependiendo de la naturaleza de las fuerzas, puede ser imposible demostrar formalmente que una solución equilibra de forma óptima (esto es, resuelve) un sistema de fuerzas. Por ejemplo, si una fuerza es el impacto social o político, no puede probarse analíticamente que una solución es óptima (al menos, no con las técnicas matemáticas actuales). Por último, contexto es el entorno en que se enmarca un sistema de fuerzas. Un contexto dado puede hacer inservibles ciertas configuraciones de software o hacer que unas sean preferible a otras. En definitiva, la definición de Coplien plantea que un patrón es una descripción genérica de una solución recurrente para un problema recurrente condicionado por la consecución de ciertos objetivos sujetos a ciertas restricciones. El concepto de patrón en el sentido que usamos aquí procede del campo de la arquitectura: fue el arquitecto Christopher Alexander quien primero manejó el término, basándose en los temas recurrentes en el diseño de edificios y calles; más tarde, la idea fue transplantada al dominio del diseño de software. Cuando analizó la historia de la Arquitectura, Alexander encontró diseños y construcciones que se repetían en zonas geográficas muy alejadas y de culturas muy distintas. Por ejemplo, los tejados de las casas de los pueblos pequeños eran y son muy similares (están hechos de tejas, tienen una gran pendiente), aunque las necesidades de los habitantes han debido ser muy distintas (éstas dependen de factores geográficos, climáticos y personales). La persistencia de estos tejados es un indicio de que existe una solución óptima (un patrón) para la construcción de tejados que se ha venido copiando, con pequeñas variaciones y quizás de forma inconsciente, en muchos pueblos del mundo. Según Alexander, Cada patrón describe un problema que ocurre una y otra vez en nuestro ambiente y después describe la parte más importante de la solución a ese problema de tal manera que se pueda utilizar esta solución un millón de veces, sin hacerlo dos veces de la misma manera. En uno de sus libros [3], aparece como ejemplo de patrón de diseño arquitectónico el de las "Ventanas de la Calle", que reproduzco aquí. Copyright (c) 2004-2014, Miguel Ángel Abián. Este documento puede ser distribuido solo bajo los términos y condiciones de la licencia de Documentación de javaHispano v1.0 o posterior (la última versión se encuentra en http://www.javahispano.org/licencias/). Page 1 of 24 El patrón Estrategia 21/05/2014 file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

Upload: torrubia

Post on 09-Jul-2015

88 views

Category:

Software


1 download

DESCRIPTION

Artículo sobre el patrón Estrategia (Strategy), un patrón de diseño para el desarrollo de software. Incluye ejemplos en Java. Autor: Miguel Ángel Abián Publicado originalmente en javaHispano.

TRANSCRIPT

El patrón Estrategia

Fecha de la última revisión: 21.05.2014 Revisión 3.0 (21.05.2014)

Miguel Ángel Abián

1. Introducción

En este artículo se expone el patrón Estrategia desde dos puntos de vista: el teórico y el práctico. Por un lado, se explican los argumentos teóricos para usar este patrón; por otro, se dan algunos consejos para saber cuándo conviene usarlo y se exponen tres ejemplos completos de su uso en Java. Para seguir el artículo, no se necesita ningún conocimiento previo que no sea comprender, aun someramente, el lenguaje Java.

Según la última versión del diccionario de la Real Academia Española, un patrón es "el modelo que sirve de muestra para sacar otra cosa igual". Aunque la definición no usa ningún término informático, se adecua bastante bien al sentido con que se emplea patrón en la ingeniería del software. En esta disciplina, los patrones no son muestras físicas o ejemplares a partir de los cuales se puedan construir cosas iguales; sino representaciones abstractas que sirven para especificar y documentar soluciones recurrentes a ciertos problemas habituales.

Pese a lo mucho que se usan los patrones de software, no abundan las definiciones del término "patrón de software". Más aún: incluso el creador del término patrón en el sentido de buena práctica (aplicado a la arquitectura, no a la construcción de software) parecía sentirse más cómodo escribiendo un libro voluminoso, abstracto y, a ratos, confuso que definiendo de forma clara y precisa el concepto de patrón.

Una definición que no hará mal a nadie es la que aparece en [1]:

[Un patrón] es una descripción en un formato fijo de cómo resolver un cierto tipo de problemas.

Según Brad Appleton (la definición la leí hace cuatro o cinco años en http://www.enteract.com/~bradapp/docs/pattern-intro.html; la dirección ya no funciona), un patrón es

[...] una unidad de información instructiva con nombre, la cual captura la estructura esencial y la compresión de una familia de soluciones exitosas probadas para un problema recurrente que ocurre dentro de un cierto contexto y de un sistema de fuerzas.

La definición más completa que conozco para el término aparece en [2] y es obra de James Coplien:

Un patrón es una regla constituida por tres partes; la cual expresa una relación entre un cierto contexto, un cierto sistema de fuerzas que ocurren repetidamente en ese contexto y una cierta configuración de software que permite a estas fuerzas resolverse a sí mismas.

Según esta definición, tres son las partes fundamentales de un patrón: un sistema de fuerzas, un contexto y una configuración de software.

Sistema de fuerzas es el término que usa Coplien como generalización del tipo de criterios que usan los ingenieros de software y los programadores para argumentar sus diseños e implementaciones. Cada vez que se diseña o implementa un componente de software, las fuerzas son los objetivos y restricciones que deben considerar ingenieros y programadores para el componente. Hallar una solución de software para un problema es solucionar el sistema de fuerzas asociado al problema. Una fuerza puede ser la velocidad de ejecución, el consumo de memoria RAM, la interoperabilidad con otros programas, la existencia de errores, etc.

La expresión configuración de software es usada por Coplien para denotar cualquier conjunto de reglas de diseño susceptible de ser utilizado para solucionar un sistema de fuerzas. Solucionar un sistema de fuerzas es encontrar un equilibrio óptimo entre las fuerzas que afectan al sistema. Dependiendo de la naturaleza de las fuerzas, puede ser imposible demostrar formalmente que una solución equilibra de forma óptima (esto es, resuelve) un sistema de fuerzas. Por ejemplo, si una fuerza es el impacto social o político, no puede probarse analíticamente que una solución es óptima (al menos, no con las técnicas matemáticas actuales).

Por último, contexto es el entorno en que se enmarca un sistema de fuerzas. Un contexto dado puede hacer inservibles ciertas configuraciones de software o hacer que unas sean preferible a otras.

En definitiva, la definición de Coplien plantea que un patrón es una descripción genérica de una solución recurrente para un problema recurrente condicionado por la consecución de ciertos objetivos sujetos a ciertas restricciones.

El concepto de patrón en el sentido que usamos aquí procede del campo de la arquitectura: fue el arquitecto Christopher Alexander quien primero manejó el término, basándose en los temas recurrentes en el diseño de edificios y calles; más tarde, la idea fue transplantada al dominio del diseño de software. Cuando analizó la historia de la Arquitectura, Alexander encontró diseños y construcciones que se repetían en zonas geográficas muy alejadas y de culturas muy distintas. Por ejemplo, los tejados de las casas de los pueblos pequeños eran y son muy similares (están hechos de tejas, tienen una gran pendiente), aunque las necesidades de los habitantes han debido ser muy distintas (éstas dependen de factores geográficos, climáticos y personales). La persistencia de estos tejados es un indicio de que existe una solución óptima (un patrón) para la construcción de tejados que se ha venido copiando, con pequeñas variaciones y quizás de forma inconsciente, en muchos pueblos del mundo. Según Alexander,

Cada patrón describe un problema que ocurre una y otra vez en nuestro ambiente y después describe la parte más importante de la solución a ese problema de tal manera que se pueda utilizar esta solución un millón de veces, sin hacerlo dos veces de la misma manera.

En uno de sus libros [3], aparece como ejemplo de patrón de diseño arquitectónico el de las "Ventanas de la Calle", que reproduzco aquí.

Copyright (c) 2004-2014, Miguel Ángel Abián. Este documento puede ser distribuido solo bajo los términos y condiciones de la licencia de Documentación de javaHispano v1.0 o posterior (la última versión se encuentra en http://www.javahispano.org/licencias/).

Page 1 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

Los patrones de software se pueden dividir en distintas categorías. En el libro [4], los patrones se clasifican en:

� Patrones de diseño. Ofrecen esquemas para refinar subsistemas y componentes de un sistema de software, o las relaciones entre ellos. Por lo general, describen una estructura de comunicación recurrente entre componentes, que sirve para resolver un problema general de diseño en un contexto determinado.

� Patrones arquitectónicos. Expresan una organización estructural básica para un sistema de software, organización referida a un conjunto de subsistemas predefinidos. Asimismo, especifican las responsabilidades de estos subsistemas y proporcionan directrices y reglas para organizar las relaciones entre ellos.

� Patrones de implementación (idioms). Estos patrones son de bajo nivel de abstracción, pues están asociados a lenguajes de programación concretos; describen cómo implementar ciertas características de los componentes de software o de sus relaciones haciendo uso de las estructuras y propiedades de un lenguaje de programación. Por ejemplo, en C++, el código while (*destino++ = *src++); es un patrón de implementación para copiar cadenas de caracteres.

Según la bibliografía técnica, los patrones de diseño ofrecen las siguientes ventajas:

� Facilitan la reutilización del diseño. Usando patrones de diseño, se pueden reusar diseños de software en tantas aplicaciones como se quiera, aun cuando éstas se escriban en distintos lenguajes de programación.

� Simplifican el proceso de diseño. Si en la fase de análisis uno encuentra un problema para el cual existe un patrón de diseño, ya tiene hecha más de la mitad del trabajo: no necesita comenzar el diseño desde cero.

� Facilitan la transmisión de los conocimientos adquiridos por otros programadores. Usando patrones de diseño, los programadores neófitos pueden aprender rápidamente cómo piensan los diseñadores expertos.

� Proporcionan un vocabulario común y consistente para el diseño de software. Esta característica facilita la comunicación entre diseñadores. Hablar de patrones es más abstracto que hablar de clases o interfaces.

� Acercan el desarrollo de software a lo que se espera de una ingeniería. El desarrollo de un vocabulario común y de una lista de buenas prácticas es común a cualquier ingeniería.

� Muestran buenos principios de diseño. Principios como el encapsulado, la abstracción y la cohesión entran desde el principio en los patrones de diseño.

� Ayudan a comprender las bibliotecas de clases que los usan. Java, por ejemplo, tiene muchos paquetes que se han construido basándose en patrones de diseño.

El libro más influyente en cuanto a patrones de diseño en software es [5], que define patrón de diseño como "una descripción de clases y objetos que se comunican entre sí, adaptada para resolver un problema de diseño general en un contexto particular". Es un libro nuevo, pero no original: clasifica y organiza unas ideas que ya llevaban cierto tiempo en el aire. Debido a su popularidad y predicamento, este libro es de lectura obligada para quien quiera asomarse al mundo de los patrones. En él se distinguen tres tipos de patrones de diseño para software: patrones de creación, de comportamiento y estructurales:

� Patrones de creación. Son patrones que abstraen el proceso de creación de los objetos. Se dividen en patrones de creación de clases (el patrón Método de fábricas -Factory Method-, por ejemplo) y de creación de objetos (el patrón Constructor -Builder-, p. ej.).

� Patrones de comportamiento. Estos patrones asignan responsabilidades y algoritmos a los objetos. Se dividen en patrones de comportamiento de objetos (el patrón Observador -Observer-, por ejemplo) y de comportamiento de clases (el patrón Método de plantillas -Template Method-, p. ej.).

Figura 1. Un patrón de diseño arquitectónico: las ventanas de la calle. Una casa sin ventanas es fría y poco acogedora. Asimismo, es desagradable estar en un casa que dé a una calle sin ninguna ventana

Page 2 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

� Patrones estructurales. Son patrones que definen cómo deben combinarse las clases y los objetos para solucionar eficazmente ciertos problemas. Se dividen en patrones estructurales de objetos (el patrón Adaptador -Adapter-, por ejemplo) y en patrones estructurales de clases (el patrón en patrones estructurales de clases (el patrón Compuesto o Entidad compuesta -Composite-, p. ej.).

En javaHispano existe una serie de artículos sobre patrones [6], obra de Alberto Molpeceres; así como un tutorial muy completo[7], obra de Martín Pérez. Si el lector desea conocer más patrones, le recomiendo leer estos trabajos. El presente artículo es una acotación marginal a ellos.

2. El patrón Estrategia

Según [5], el patrón Estrategia "define una familia de algoritmos, encapsula cada uno y los hace intercambiables. La Estrategia deja que el algoritmo varíe independientemente de los clientes que lo usen" (página 315).

Me he decidido a escribir sobre este patrón por varios motivos:

� Es un patrón muy útil para seguir el Principio Abierto/Cerrado ("Una entidad de software -clase, módulo, etcétera- debe estar abierta para su extensión, pero cerrada para su modificación").

� Muestra el poder del polimorfismo en los lenguajes orientados a objetos.

� Al basarse en el encapsulado y en la programación de interfaces, constituye un buen ejemplo de la aplicación de los principios del diseño orientado a objetos.

En el principio Abierto/Cerrado, "debe estar abierta para su extensión" significa que no se ha de cambiar una clase existente cuando se extiendan sus funciones; "cerrada para su modificación" significa que deben existir subclases o clases abstractas o interfaces en las que delegar las nuevas funciones que se desee añadir. Para que el principio Abierto/Cerrado no quede en mera palabrería o en una montaña de buenas intenciones, se necesitan varios ingredientes: abstracción, encapsulado, polimorfismo, herencia e interfaces. No por casualidad, estas herramientas son el núcleo de la verdadera programación orientada a objetos. Sin ellas, un programa escrito en Java, Smalltalk o C++ sería tan orientado a objetos como melódico es el chirriar de una bisagra oxidada.

Para entender las implicaciones del principio Abierto/Cerrado, se puede considerar el siguiente código, correspondiente a un método que calcula el importe total de la compra en un supermercado:

public double calcularImporteVenta(Producto product os[]) { double importeTotal = 0.00; for (i=0; i < productos.length; i++) { importeTotal += producto[i] * getPrecio(); } return importeTotal; }

Tal y como está escrito el método CalcularImporteVenta(), cumple perfectamente el principio Abierto/Cerrado. Consideremos ahora que la inflación se está disparando en el país donde se encuentra el supermercado y que sus dueños, comprometidos con el bienestar del país y con los estrictos criterios macroeconómicos del Fondo Monetario Internacional, deciden rebajar el precio de los alimentos que más influyen sobre la inflación: el pan y la carne de pollo. Sí, algo de ironía hay en el ejemplo; pero ¿no es irónico que una institución de países industrializados proponga políticas y prácticas antiproteccionistas que ningún país industrializado ha seguido nunca? En fin, el código del método anterior quedaría así:

public double calcularImporteVenta(Producto product os[]) { double importeTotal = 0.00; for (i=0; i < productos.length; i++) { if (productos[i] instanceof BarraPan) importeTotal += producto[i] * 0.85 * ge tPrecio(); else if (productos[i] instanceof Pollo) importeTotal += producto[i] * 0.90 * ge tPrecio(); else importeTotal += producto[i] * getPrecio (); } return importeTotal; }

Este código ya no cumple el principio Abierto/Cerrado. Si la lista de productos alimentarios que influyen en la inflación cambiara, habría que añadir nuevos productos y eliminar otros. Se incumpliría, pues, la condición de cerrado para modificaciones. Tal como se verá en la sección 4, el patrón Estrategia permite cumplir el principio.

La representación UML del patrón Estrategia se muestra en la figura 2.

Page 3 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

Las clases que aparecen en la figura se explican aquí:

� Contexto. Un objeto Contexto es un cliente que usa una operación de la interfaz ofrecida por EstrategiaAbstracta. Por tanto, mantiene una referencia a un objeto que es instancia de alguna clase de las derivadas por herencia de la clase EstrategiaAbstracta. Contexto desconoce qué clases implementarán la operación o cómo lo harán.

� EstrategiaAbstracta. Esta clase define el comportamiento fundamental de las clases Estrategia. Al ser una clase abstracta o una interfaz (en Java o C#), no pueden crearse instancias de ella. Para realizar operaciones, los clientes deben usar instancias de sus subclases concretas.

� EstrategiaConcreta1, EstragegiaConcreta2, etc. Las instancias de estas clases especializan el comportamiento básico especificado por EstrategiaAbstracta. La especialización se consigue mediante la implementación de las operaciones de EstrategiaAbstracta o la definición de otras nuevas. Los objetos cliente tienen referencias a instancias de estas clases.

En Java, las subclases de EstrategiaAbstracta se derivan de ésta mediante extends (si ésta es una clase abstracta) o implements (si es una interfaz). Existen situaciones sencillas en las que la clase EstrategiaAbstracta puede ser sustituida por una clase que implementa los comportamientos deseados; en este caso, no se necesitan las clases EstrategiaConcreta1, EstragegiaConcreta2, etc. Al final de la siguiente sección se pondrá un ejemplo de esta situación, la cual correspondería a lo que puede denominarse "patrón Estrategia degenerado".

Cuando se aplica a un problema, el patrón Estrategia rompe el diseño en dos partes claramente diferenciadas: la del Contexto y la de la Estrategia. La primera parte es la parte invariante del diseño, pues su comportamiento no cambia de vez en vez. La segunda (formada por la clase EstrategiaAbstracta y sus subclases) aprehende la naturaleza cambiante y modificable del diseño: cada subclase de EstrategiaConcreta define un comportamiento específico que puede necesitarse cuando el programa esté en ejecución. En cierto modo, el patrón Estrategia aprovecha el mecanismo de la herencia para sacar factor común de los distintos algoritmos.

Existen varios modos de colaboración entre la Estrategia y el Contexto que permiten elegir el comportamiento deseado en cada ocasión. Los dos más habituales se detallan aquí:

� Un objeto Contexto puede pasar a un objeto Estrategia, cuando sea necesario, todos los datos necesarios para el método que implementa el comportamiento deseado. La gran ventaja de trabajar así radica en que se consigue desacoplar el Contexto de la Estrategia.

� Un Contexto puede pasar una referencia a sí mismo como argumento de los métodos que implementan las operaciones de la interfaz de Estrategia. De este modo, la Estrategia puede llamar al Contexto cada vez que lo necesite. La desventaja de obrar así es que se aumenta el acoplamiento entre Contexto y Estrategia.

Normalmente, los clientes que necesitan las operaciones de la Estrategia no acceden directamente a los métodos que las implementan, sino que interaccionan solamente con el Contexto (que, a su vez, es un cliente de la Estrategia). Los clientes crean y pasan objetos del tipo EstrategiaConcreta a objetos Contexto, que se encargan de pasar las peticiones a la Estrategia.

3. Usos del patrón Estrategia

De modo general, resulta conveniente emplear el patrón Estrategia cuando los clientes implementan algoritmos genéricos que son difíciles de reusar, de intercambiar y cuya elección puede variar en tiempo de ejecución. Un buen indicio de lo anterior lo da la proliferación

Figura 2. Estructura del patrón Estrategia

Page 4 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

cancerosa de expresiones condicionales en el código (if, switch/case).

Por ejemplo, consideremos que se quiere escribir una aplicación encargada de cifrar texto mediante claves. Dada una clave y un texto, la aplicación debe generar un texto indescifrable para cualquiera que no tenga la clave con la que se codificó el texto. Consideremos también que existen varias implementaciones para el algoritmo de cifrado. Los motivos para esta multiplicidad no son teóricos: dependiendo de la longitud del texto, puede haber implementaciones más eficaces que otras, pueden existir implementaciones más rápidas pero que consuman más recursos (memoria, espacio en el disco duro, etc.)...

Una solución a nuestro problema sería colocar toda la lógica del negocio en la clase cliente (la que usa los algoritmos). Mediante sentencias condicionales, el código del cliente elegiría el algoritmo de cifrado que necesitara. Esta solución dista de ser idónea: según se fueran incorporando nuevas implementaciones del algoritmo, aumentaría el número de sentencias condicionales y se necesitaría modificar el código del cliente, así como recompilarlo. Aparte, sería imposible cifrar dinámicamente un texto mediante diferentes algoritmos.

Otra solución consistiría en escribir una clase general cuyas subclases encapsularan cada una de las implementaciones del algoritmo de cifrado. Los problemas de esta otra solución no serían despreciables: seguiría siendo necesario mantener las sentencias condicionales, se dispararía el número de subclases y el comportamiento seguiría fijado en tiempo de compilación. Un objeto, pongamos por caso, Implementacion1 no podría comportarse como un objeto Implementacion2 durante la ejecución del programa: una vez creado, su comportamiento sería inmodificable.

Para este problema, el patrón Estrategia proporciona una excelente solución. Basta seguir estos pasos:

� Identifíquese el comportamiento deseado (cifrado del texto mediante una clave).

� Especifíquese la interfaz del comportamiento en una clase abstracta o en una interfaz (EstrategiaAbstracta).

� Desplácese todo el código basado en sentencias condicionales a cada una de las subclases concretas de EstrategiaAbstracta.

� Cada vez que se necesite una implementación concreta del algoritmo de cifrado, deléguese la petición a un objeto (Contexto) que contenga referencias a objetos del tipo EstrategiaAbstracta.

Con el patrón Estrategia, cada vez que se necesita añadir nuevas implementaciones del algoritmo de cifrado, no se precisará modificar el cliente ni el Contexto: sólo habrá que añadir una nueva clase EstrategiaConcreta. Como los clientes usan la interfaz de métodos definida por EstrategiaAbstracta (es decir, su tipo, no la clase), un mismo objeto Texto podrá ser cifrado con todas las implementaciones.

¿Cuándo conviene usar el patrón Estrategia?

� Cuando hay muchas clases interrelacionadas que difieren solamente en comportamiento. Este patrón proporciona un modo de configurar el comportamiento deseado sin modificar el tipo de cada objeto.

� Cuando se necesitan distintas variantes de un mismo algoritmo.

� Cuando un algoritmo usa datos que no queremos que sean visibles para los clientes. El patrón Estrategia permite evitar que los clientes conozcan las estructuras de datos que se usen internamente.

He aquí algunas situaciones en las que se cumple una o varias de las condiciones expuestas arriba:

� Representación gráfica de unos mismos datos en distintas formas (gráfico de barras, de porciones, etc.).

� Compresión de ficheros en distintos formatos (Zip, GZip, Rar, etc.).

� Grabación de ficheros en distintos formatos (HTML, PDF, Word, etc.).

� Utilización de distintas políticas de seguridad en el acceso a los recursos de una red.

� Encaminamiento de paquetes IP mediante diversos algoritmos (la elección dependerá del estado de las redes, del tráficos de datos, de la distancia entre redes, etc.).

� Ordenación o búsqueda de elementos en una colección por medio de distintos algoritmos (método de la burbuja, búsqueda secuencial, etc.)

4. Algunos ejemplos del patrón Estrategia

No necesitamos recurrir al software para ver ejemplos del patrón Estrategia. La propia realidad nos ofrece unos cuantos. Consideremos, por ejemplo, una agencia de viajes; en la figura 3 se muestra su representación UML.

Page 5 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

Como es bien sabido, una agencia de viajes ofrece el servicio de proporcionar viajes a sus clientes. Mediante ella, una persona puede seleccionar el viaje deseado (medio de transporte, precio, destino) entre los que la agencia pone a disposición de los clientes. Según el patrón Estrategia, el cliente interacciona con la agencia de viajes para seleccionar el comportamiento deseado para su viaje.

El patrón Estrategia también puede usarse para plantear asuntos más existenciales. Vivimos en una época en que la adolescencia se prolonga hasta los treinta, en que la juventud es el valor supremo y en que algunas empresas se jactan de que la media de edad de sus plantillas no supera los treinta y cinco años (empresas, por cierto, que omiten dar la edad media de sus directivos: si tanto importa la juventud del personal, ¿por qué no tienen directores y presidentes con menos de cincuenta años?). El patrón Estrategia también sirve para recordarnos el orden natural de las cosas:

Figura 3. Una agencia de viajes

Page 6 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

Dependiendo de la edad de una persona, la Vida elige una especialización de Persona (Niño, Adulto o Anciano). El patrón Estrategia permite que estas especializaciones añadan sus propios comportamientos especializados al comportamiento base definido en Persona: trabajar(), balbucear(), cobrarPension().

Si nos restringimos a la aplicación del patrón Estrategia al software, podemos considerar el ejemplo que aparece en [5] (página 315), consistente en un programa para dividir un texto en líneas (por ejemplo, con espaciado sencillo o doble). Gamma et al. razonan que resulta desaconsejable escribir el código de los algoritmos de inserción de saltos de línea dentro de las clases que necesiten dichos algoritmos. Tres son los motivos aducidos:

� Los clientes se volverían más complejos si incluyeran el código de todos los algoritmos para colocar los saltos de línea.

� Sería difícil modificar los algoritmos existentes y añadir nuevos.

� Algunos clientes tendrían algoritmos para la inserción de saltos de línea que raras veces, o nunca, usarían.

La solución que proponen Gamma et al. consiste en aplicar el patrón Estrategia del modo representado en la figura 5.

En este ejemplo, la clase Composition mantiene una colección de instancias Component, que representan texto y elementos gráficos en un documento. Un objeto Composition organiza los objetos componentes dependiendo de la manera de insertar saltos de línea que se adopte. La clase Composition se encarga de mantener y actualizar los saltos de línea del texto que se muestra en un visor de texto. Cada estrategia para aplicar los saltos de línea se implementa en subclases de la clase abstracta Compositor:

� La clase SimpleCompositor implementa una estrategia simple que determina los saltos de línea de uno en uno.

� La clase TeXCompositor implementa el algoritmo TeX para encontrar saltos de línea (se buscan y actualizan saltos de línea párrafo a párrafo).

Figura 4. La vida segun el patrón Estrategia

Figura 5. Ejemplo extraído del libro Design Patterns: Elements of Reusable Object-Oriented Software

Page 7 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

� La clase ArrayCompositor implementa una estrategia que selecciona los saltos de línea de modo que cada columna tenga un número fijo de elementos.

Un objeto Composition (que representa un bloque de texto) mantiene una referencia a un objeto Compositor. Cuando un objeto Composition vuelve a componer su texto, delega esta responsabilidad en el objeto Compositor asociado. Los clientes de Composition (un procesador de textos, por ejemplo), especifican qué Compositor se usará eligiendo el objeto Compositor al cual se hará referencia dentro del Composition.

Otro ejemplo de uso del patrón Estrategia nos lo proporciona el paquete java.awt. Las instancias de la clase Component del AWT declaran las instancias de la clase Contexto que permiten seleccionar y establecer distintos algoritmos de distribución de los componentes gráficos mediante el método SetLayout() de la interfaz LayoutManager (la clase EstrategiaAbstracta para este ejemplo), encargada de distribuir los componentes gráficos en el componente AWT. Las subclases de LayoutManager (BorderLayout, BoxLayout, FlowLayout, ScrollPaneLayout, etc.) encapsulan su propia implementación de setLayout().

Si consideramos la aplicación de la sección anterior que se encargaba de cifrar texto mediante claves, el patrón Estrategia nos daría una solución como la que se representa en la figura:

Figura 6. El patrón Estrategia en el AWT

Page 8 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

Sin el patrón Estrategia, la implementación del programa de codificación de textos tendría un método similar a éste:

public void cifrar(String clave, String bloqueTexto , int algoritmo) { // Método de la clase Texto, que representa textos que van a codificarse. if (algoritmo == ALGORITMO_X) CifrarConAlgoritmoX(clave, bloqueTexto); if (algoritmo == ALGORITMO_y) CifrarConAlgoritmoY(clave, bloqueTexto); ... }

Con el patrón Estrategia, el método quedaría así:

public setAlgoritmoCifrado(Cifrado cifrado) { // Método de la clase Texto, que representa tex tos que van a cifrarse. // Se escoge el algoritmo de cifrado en tiempo de ejecución. this.cifrado = cifrado; } public void cifrar(String clave, String bloqueTexto ) { // Método de la clase Texto, que representa tex tos que van a cifrarse. cifrado.codificar(clave, bloqueTexto); }

Cualquier videojuego donde se pueda seleccionar un personaje según la etapa del juego, el entorno, las características de los enemigos, etc., es un buen ejemplo del patrón Estrategia. El cliente que elige, dependiendo de las condiciones, un personaje dado está eligiendo una estrategia concreta, que hereda de una estrategia abstracta, con métodos básicos y comunes para todas sus subclases. Los programadores de un videojuego no programan diferentes algoritmos de localización de los objetos y de representación gráfica de los mismos. Dicho de otro modo: la manera de dibujar los píxeles de un ogro o de una encantadora dama no varía, ni tampoco varían los algoritmos generales como los de detección de colisiones, etc. Todos los personajes tienen los mismos. Ahora bien, dependiendo del personaje, cambian los píxeles que lo representan, así como el comportamiento frente a los ataques del enemigo, la resistencia, la habilidad con ciertas armas, etc. Esos comportamientos específicos se definen en las estrategias concretas, mientras que en la estrategia abstracta se definen los comportamientos generales.

En la figura 8 se muestra el patrón Estrategia para un videojuego donde el jugador puede elegir un personaje humano, leonino o batracio. Cada Estrategia concreta (Humano, Leon, Batracio) tendrá su propia implementación de los métodos luchar(arma, energia, duracionAtaque) y huir (velocidadHuida), pues es de suponer que una rana no huirá con la misma gracia y velocidad que un león; pero las tres subclases recurriran a los métodos de la superclase Personaje para métodos como dibujarPixel(x, y), borrarPixel(x, y), MoverPixel(x_inic, y_inic, x_dest, y_dest), etc.

Figura 7. El patrón Estrategia en el ejemplo de cifrado

Page 9 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

Por último, otro ejemplo de la aplicación del patrón Estrategia lo proporciona el ejemplo de los productos de un supermercado que se vio en la sección 2. En lugar del código que no cumple el principio Abierto/Cerrado, el patrón permite escribir este código:

public class Producto { private String nombre; private double precio; private Descuento descuento; public Producto(String nombre) { this.nombre = nombre; } public void setDescuento(Descuento descuento) { this.descuento = descuento; } public void setPrecio(double precio) { this.precio = precio; } public double getPrecio() { return descuento.getPrecio(precio); } }

public class Descuento { private double factor; //factor de descuento public Descuento(double factor) { this.factor = factor; } public double getPrecio(double precio) { return precio * factor; } }

En este ejemplo, la clase EstrategiaAbstracta es una clase que implementa directamente sus métodos (es decir, no es abstracta ni una interfaz); en consecuencia, son innecesarias sus subclases. Con esta solución se puede cambiar dinámicamente los descuentos de cualquier producto del supermercado, sin modificar el código ya escrito. Por ejemplo, si se quiere fijar el descuento del pollo en un 10%, bastará escribir:

Producto pollo = new Producto("pollo"); pollo.setPrecio(30.05);

Figura 8. El patrón Estrategia en un videojuego

Page 10 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

pollo.setDescuento(new Descuento(0.90));

A la vista del código de las dos últimas clases, se puede formular lo que se conoce como un corolario del principio Abierto/Cerrado: "Si un sistema de software debe permitir un conjunto de alternativas, lo ideal es que una sola clase en el sistema conozca el conjunto completo de alternativas".

5. Tres ejemplos de implementación en Java del patrón Estrategia

En esta sección se exponen de forma completa tres ejemplos de uso del patrón Estrategia con Java. Los dos primeros son versiones simplificadas de situaciones ante las que me he encontrado en mi actividad profesional. Son, pues, aplicaciones completamente prácticas del patrón. Innegable es que usar este patrón exige un poco de reflexión y planificación previas; pero creo que merece la pena: uno ahorra mucho tiempo cuando tiene que introducir cambios o mejoras. Y no hay que engañarse: los clientes siempre piden mejoras, modificaciones o refinamientos para sus aplicaciones (está en su naturaleza). Anticiparse a esos cambios de manera que se modifique lo menos posible el código o los módulos ya escritos suele ser una buena práctica (al menos, si uno no cobra por horas de trabajo).

El tercer ejemplo se aleja mucho del mundo empresarial: consiste en usar el patrón Estrategia para modelar osciladores amortiguados clásicos (no cuánticos). Aparte de para estudiar muelles y resortes, los osciladores amortiguados son muy útiles para analizar estructuras periódicas de átomos o moléculas. De hecho, el ejemplo se me ocurrió cuando analizaba los modos de vibración de una red de difracción tridimensional.

5.1. Un programa de cálculo de precios con descuentos

Consideremos que se nos encarga una aplicación de contabilidad para una tienda de muebles que clasifica sus productos en tres tipos: mueble clásico, mueble kit y mueble de diseño. Dependiendo del tipo de mueble, la tienda hace un descuento por cada venta: mueble clásico, un 5%; mueble kit, un 10%; y mueble moderno, un 15%.

Supongamos también que desconocemos el patrón Estrategia y que somos novatos en la programación orientada a objetos. Entonces, nuestro primer intento sería algo como (omito los métodos get y set que no voy a usar y casi todos los comentarios):

package mobiliario; /** * Intento sin el patrón Estrategia: clase Mueble. * Representa un mueble del catálogo de la tienda. * */ public class Mueble { private double precio; // Precio del catálogo. private String codigo; // Código en el catálogo . private int tipo; // Tipo de mueble: clásico, k it o moderno. public static final int CLASICO = 0; public static final int KIT = 1; public static final int MODERNO = 2; public double getPrecio() { return precio; } public String getCodigo() { return codigo; } public int getTipo() { return tipo; } public Mueble(String codigo, double precio, int tipo) throws ExcepcionTipoMueble { if ( (tipo > Mueble.MODERNO) || (Mueble.CLA SICO > tipo) ) throw new ExcepcionTipoMueble(); else { this.codigo = codigo; this.precio = precio; this.tipo = tipo; } } }

package mobiliario; /** * Intento sin el patrón Estrategia: clase VentaMue ble. * Esta clase representa la venta de un mueble del catálogo de la tienda */ import java.util.*; public class VentaMueble { private Date fecha; // Fecha de la venta. private Mueble mueble; // Mueble que se vende.

Page 11 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

public Date getFecha() { return fecha; } public VentaMueble(Mueble mueble){ fecha = new Date(); this.mueble = mueble; } public double calcularPrecioVenta() throws Exc epcionTipoMueble { // El precio de venta tiene un descuento c on respecto al del catálago. if (mueble.getTipo()==Mueble.CLASICO){ return (mueble.getPrecio())*0.95; } else if (mueble.getTipo()==Mueble.KIT){ return (mueble.getPrecio())*0.90; } else if (mueble.getTipo()==Mueble.MODERNO) { return (mueble.getPrecio())*0.85; } else throw new ExcepcionTipoMueble(); } // Ejemplo de funcionamiento del programa. public static void main(String args[]) throws E xcepcionTipoMueble { VentaMueble vm = new VentaMueble (new Muebl e("Sofá Dalí", 1800.65, Mueble.MODERNO)); System.out.println("Precio de venta: " + vm .calcularPrecioVenta()); } }

package mobiliario; /** * Intento sin el patrón Estrategia: clase Exceptio nTipoMueble. * Esta excepción se lanza cuando se introduce un t ipo de mueble inválido. * */ public class ExcepcionTipoMueble extends Exception { public ExcepcionTipoMueble() { super("Compruebe el tipo de mueble introduc ido."); } }

Los problemas de introducir toda la lógica del negocio en el código del cliente ya se vieron, de modo general, en la sección 3. En este ejemplo, los problemas concretos son dos:

� Si se introdujera un nuevo tipo de mueble en el catálogo de la tienda, habría que modificar el código del cliente. � Si se cambiaran los descuentos (por ejemplo, en época de rebajas), habría que modificar el código del cliente. El cliente está

fuertemente acoplado con la política de descuentos.

Nuestra primera solución es muy inestable: mínimos cambios llevan a reprogramar el cliente y a recompilarlo. Si utilizamos el patrón Estrategia, obtendríamos una solución similar a ésta (vuelvo a omitir los métodos get y set que no voy a usar y casi todos los comentarios):

package mobiliario; /** * Interfaz Descuento (Solución con el patrón Estra tegia). * Esta interfaz es la estrategia abstracta. */ public interface Descuento { public double calcularPrecioVenta(double precio ); }

package mobiliario; /** * Interfaz DescuentoMuebleClasico (Solución con el patrón Estrategia). * Esta clase es una de las estrategias concretas. */ public class DescuentoMuebleClasico implements Desc uento { // Constructor public DescuentoMuebleClasico() { } // Devuelve el precio con el descuento. public double calcularPrecioVenta(double precio ) { return (precio * 0.95); }

Page 12 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

}

package mobiliario; /** * Interfaz DescuentoMuebleKit (Solución con el pat rón Estrategia). * Esta clase es una de las estrategias concretas. */ public class DescuentoMuebleKit implements Descuent o { // Constructor public DescuentoMuebleKit() { } // Devuelve el precio con el descuento. public double calcularPrecioVenta(double precio ) { return (precio * 0.90); } }

package mobiliario; /** * La interfaz DescuentoMuebleModerno (Solución con el patrón Estrategia). * Esta clase es una de las estrategias concretas. */ public class DescuentoMuebleModerno implements Desc uento { // Constructor public DescuentoMuebleModerno() { } // Devuelve el precio con el descuento. public double calcularPrecioVenta(double precio ) { return (precio * 0.85); } }

package mobiliario; /** * Clase Mueble (solución con el patrón Estrategia) . * Representa un mueble del catálogo de la tienda. * Esta clase es el contexto. */ public class Mueble { private String codigo; // Código en el catálogo . private double precio; // Precio del catálogo. private Descuento desc; // Tipo de descuento. public double getPrecio() { return precio; } public String getCodigo() { return codigo; } public Descuento getDescuento() { return desc; } public Mueble(String codigo, double precio, Des cuento desc) throws Exception { if ( (desc == null) ) { // Cuando se crea un objeto Mueble, es obligatorio introducir su descuento. throw new Exception("Debe introducir un tipo de descuento"); } else { this.codigo = codigo; this.precio = precio; this.desc = desc; } } }

package mobiliario; /** * Clase VentaMueble (solución con el patrón Estrat egia). * Esta clase representa la venta de un mueble del catálogo de la tienda. * Es cliente de la clase Mueble (el contexto para este caso). */ import java.util.*;

Page 13 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

public class VentaMueble { private Date fecha; // Fecha de la venta. private Mueble mueble; // Mueble vendido. public Date getFecha() { return fecha; } public VentaMueble(Mueble mueble){ this.fecha = new Date(); this.mueble = mueble; } public double calcularPrecioVenta() throws Exce pcion { return (mueble.getDescuento().calcularPreci oVenta(mueble.getPrecio())); } // Ejemplo de funcionamiento del programa. public static void main(String[] args) throws E xception { Mueble m = new Mueble ("Silla Luis XVI", 13 00.67, new DescuentoMuebleClasico()); VentaMueble vm = new VentaMueble (m); System.out.println ("Código: " + m.getCodig o()); System.out.println("Precio de catálogo: " + m.getPrecio()); System.out.println("Precio de venta: " + vm .calcularPrecioVenta()); } }

La segunda solución presenta ventajas significativas con respecto a la primera: ahora podemos añadir nuevos tipos de descuento sin modificar la clase Mueble ni la clase VentaMueble. Si se quiere incluir un nuevo descuento, basta añadir una nueva clase que implemente la interfaz Descuento.

En el caso de que se desee optar por una política de descuentos personalizada para cada mueble, el patrón Estrategia permite escribir una solución como ésta:

package mobiliario; /** * Clase Descuento (Solución con el patrón Estrateg ia para el caso de políticas personalizadas * de descuentos). */ public class Descuento { private double factorDescuento; // Factor de de scuento. public Descuento (double factorDescuento) { this.factorDescuento = factorDescuento; } public double calcularPrecioVenta(double precio ) { return (factorDescuento * precio); } }

package mobiliario; /** * Clase Mueble (solución con el patrón Estrategia para el caso de políticas personalizadas * de descuentos). */ public class Mueble { private String codigo; // Código en el catálogo . private double precio; // Precio del catálogo. private Descuento desc; // Tipo de descuento. public double getPrecio() { return precio; } public String getCodigo() { return codigo; } public Descuento getDescuento() { return desc; } public Mueble(String codigo, double precio, Des cuento desc) throws Exception { if ( (desc == null) ) { // Cuando se crea un objeto Mueble, es obligatorio introducir su descuento. throw new Exception("Debe introducir un tipo de descuento"); } else { this.codigo = codigo; this.precio = precio;

Page 14 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

this.desc = desc; } } }

package mobiliario; /** * Clase VentaMueble (solución con el patrón Estrat egia para el caso de políticas personalizadas * de descuentos). */ import java.util.*; public class VentaMueble { private Date fecha; // Fecha de la venta. private Mueble mueble; // Mueble vendido. public Date getFecha() { return fecha; } public VentaMueble(Mueble mueble){ this.fecha = new Date(); this.mueble = mueble; } public double calcularPrecioVenta() throws Exce ption { return (mueble.getDescuento().calcularPreci oVenta(mueble.getPrecio())); } // Ejemplo de funcionamiento del programa. public static void main(String[] args) throws E xception { Mueble m = new Mueble ("Silla Barcelona", 4 560.18, new Descuento(0.90)); VentaMueble vm = new VentaMueble (m); System.out.println ("Código: " + m.getCodig o()); System.out.println("Precio de catálogo: " + m.getPrecio()); System.out.println("Precio de venta: " + vm .calcularPrecioVenta()); } }

5.2. Un programa de cálculo de amortizaciones

Consideremos ahora que estamos trabajando en un módulo de software para llevar la contabilidad de una empresa y que hemos llegado a la parte del cálculo de amortizaciones de los bienes de la empresa (máquinas, ordenadores, mobiliario, mercancías, etc.). Según el Plan General Contable, la amortización es "la expresión de la depreciación sistemática, anual y efectiva sufrida por el inmovilizado inmaterial y material por su incorporación al sistema productivo".

En principio, la Hacienda española admite varias maneras de calcular la amortización: la amortización mediante tablas, la amortización lineal, la amortización degresiva por suma de dígitos y la amortización degresiva en progresión decreciente. Consideremos también que la empresa para la cual trabajamos sólo emplea los tres últimos tipos de cálculo. Lógicamente, los contables de la empresa desearán poder elegir el cálculo de amortización que haga máxima, para cada bien, la amortización; así podrán pagar menos a la Hacienda pública.

El patrón Estrategia parece un buen candidato para echarnos una mano en el diseño de la parte de las amortizaciones: contamos con un comportamiento abstracto (algo así como Amortizacion) que se particulariza en comportamientos concretos (AmortizacionDegresivaDigitos, AmortizacionLineal, AmortizacionProgresionDecreciente).

Veamos una posible forma de implementar el cálculo de amortizaciones con el patrón Estrategia (incorporo documentación javadoc al código):

package amortizacion; /** * Esta interfaz es la estrategia abstracta del pat ron Estrategia. * */ public interface Amortizacion { /** * * @param precio Precio del bien. * @param valorResidual Valor del bien al final de su vida útil. * @param vida Años de vida útil del bien. * @param anyo Anualidad para la cual se quiere calcular la amortización del bien. * @return Amortización correspondiente al año anyo. * @throws Excepcion */ public double calcularAmortizacion (double prec io, double valorResidual, int vida, int anyo) throw s Excepcion; }

package amortizacion;

Page 15 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

/** * AmortizacionLineal es una estrategia concreta de l patrón Estrategia. * Implementa el cálculo de la amortización lineal (cuotas fijas). */ public class AmortizacionLineal implements Amortiza cion { /** * * @param precio Precio del bien. * @param valorResidual Valor del bien al final de su vida útil. * @param vida Años de vida útil del bien. * @param anyo Anualidad para la cual se quiere calcular la amortización del bien. * @return Amortización correspondiente al año anyo. * @throws Excepcion */ public double calcularAmortizacion (double prec io, double valorResidual, int vida, int anyo) throw s Excepcion { // Comprobación de los argumentos if ( (precio > 0) && (anyo > 0) && (vida > 0) && (vida >= anyo) && (valorResidual < precio) ) { return ( (precio - valorResidual) / vid a ); } else throw new Excepcion(); } /** * Constructor sin argumentos. */ public AmortizacionLineal() { } }

package amortizacion; /** * AmortizacionDegresivaDigitos es una estrategia c oncreta del patrón Estrategia. * Implementa el cálculo de la amortización degresi va por suma de dígitos. */ public class AmortizacionDegresivaDigitos implement s Amortizacion { /** * * @param precio Precio del bien. * @param valorResidual Valor del bien al final de su vida útil. * @param vida Años de vida útil del bien. * @param anyo Anualidad para la cual se quiere calcular la amortización del bien. * @return Amortización correspondiente al año anyo. * @throws Excepcion */ public double calcularAmortizacion (double prec io, double valorResidual, int vida, int anyo) throw s Excepcion { // Comprobación de los argumentos if ( (precio > 0) && (anyo > 0) && (vida > 0) && (vida >= anyo) && (valorResidual < precio) ) { return ( (precio - valorResidual) * ( ( vida - anyo + 1.0) / ( (vida * (vida + 1)) / 2.0) ) ); } else throw new Excepcion(); } /** * Constructor sin argumentos. */ public AmortizacionDegresivaDigitos() { } }

package amortizacion; /** * AmortizacionProgresionDecreciente es una estrate gia concreta del patrón Estrategia. * Implementa el cálculo de la amortización degresi va en progresión decreciente. */ class AmortizacionProgresionDecreciente implements Amortizacion { /** * * @param precio Precio del bien. * @param valorResidual Valor del bien al final de su vida útil. * @param vida Años de vida útil del bien. * @param anyo Anualidad para la cual se quiere calcular la amortización del bien. * @return Amortización correspondiente al año anyo. * @throws Excepcion */ public double calcularAmortizacion (double prec io, double valorResidual, int vida, int anyo) throw s Excepcion { // Comprobación de los argumentos if ( (precio > 0) && (anyo > 0) && (vida > 0) && (vida >= anyo) && (valorResidual < precio) ) { double a = valorResidual / precio; double b = (1./vida); double t = (1. - Math.pow(a, b)); return ( precio * t * Math.pow((1 - t), anyo - 1) ); } else

Page 16 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

throw new Excepcion(); } /** * Constructor sin argumentos. */ public AmortizacionProgresionDecreciente() { } }

package amortizacion; public class Excepcion extends Exception { public Excepcion() { super("Compruebe los valores introducidos en l os parametros."); } }

package amortizacion; /** * Esta clase es el contexto del patrón Estrategia. */ public class Bien { /** * Precio del bien. */ private double precio; /** * Valor del bien al final de su vida útil. */ private double valorResidual; /** * Años de vida útil del bien. */ private int vida; /** * Estrategia de amortización. */ private Amortizacion amortizacion; /** * Método set para precio. * @param precio Precio del bien. */ public void setPrecio(double precio) { this.precio = precio; } /** * Método get para valorResidual. * @return Valor residual del bien. */ public double getValorResidual() { return valorResidual; } /** * Método set para valorResidual. * * @param valorResidual double */ public void setValorResidual(double valorResidu al) { this.valorResidual = valorResidual; } /** * Método get para precio * @return Precio del bien */ public double getPrecio() { return precio; } /** * Método set para vida. * * @param vida Años de vida útil del bien. */ public void setVida(int vida) { this.vida = vida; } /** * Método get para vida. * @return Años de vida útil del bien.

Page 17 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

*/ public int getVida() { return vida; } /** * Método set para Amortizacion. * * @param amort Estrategia de amortizacion */ public void setAmortizacion(Amortizacion amort) { this.amortizacion = amort; } /** * Constructor * * @param precio Precio del bien. * @param valorResidual Valor residual del bien . * @param vida Vida útil del bien. * @param amort Estrategia de amortización. */ public Bien(double precio, double valorResidual , int vida, Amortizacion amort) { this.precio = precio; this.valorResidual = valorResidual; this.vida = vida; if (amort == null) { this.amortizacion = new AmortizacionLin eal(); } else this.amortizacion = amort; } public void CambiarTipoAmortizacion (Amortizaci on amort) { if (amort != null) setAmortizacion(amort); } public void calcularAmortizacion(int anyo) thro ws Excepcion { System.out.println(amortizacion.calcularAmo rtizacion(precio, valorResidual, vida, anyo)); } }

package amortizacion; /** * Esta clase es cliente de la clase Bien. * */ public class Cliente { // Ejemplo de uso del programa. Se calculan la amortizaciones al segundo año. public static void main(String args[]) { try { Bien bien = new Bien(60000., 5000., 10, null); bien.calcularAmortizacion(2); bien.CambiarTipoAmortizacion(new Amorti zacionProgresionDecreciente()); bien.calcularAmortizacion(2); bien.CambiarTipoAmortizacion(new Amorti zacionDegresivaDigitos()); bien.calcularAmortizacion(2); } catch (Exception e) { e.printStackTrace(); } } }

5.3. Un programa de cálculo de posiciones para osciladores amortiguados.

En el mundo físico hay muchos movimientos que son oscilatorios. El movimiento oscilatorio más sencillo es el denominado movimiento armónico simple. Cualquier sistema que experimente una fuerza restauradora lineal es candidato a ser considerado un oscilador armónico simple. La ecuación de movimiento para un oscilador armónico simple que oscila en una sola dirección es (k es la constante elástica del oscilador)

O dicho de otra manera:

La solución a la ecuación diferencial de segundo grado es la siguiente:

Page 18 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

Donde A y el ángulo phi son constantes que se determinan al especificar unas condiciones iniciales (en Matemáticas se suelen llamar condiciones de contorno).

En cualquier sistema oscilatorio real existen fuerzas de fricción que tienden a provocar que el movimiento se detenga al cabo de un tiempo (este tipo de movimiento se conoce como movimiento armónico amortiguado). La ecuación general para un oscilador armónico amortiguado es (lambda es la constante de amortiguación del oscilador)

O lo que es lo mismo:

Esta ecuación diferencial admite tres clases de soluciones:

1) Solución amortiguada (cuando gamma es menor que omega0)

La solución general es:

Si se imponen estas condiciones iniciales: t=0, x=0, v=v0, la solución queda así:

2) Solución crítica (cuando gamma es igual a omega0)

La solución general es:

Si se imponen estas condiciones iniciales: t=0, x=0, v=v0, la solución queda así:

3) Solución sobreamortiguada (cuando gamma es mayor que omega0)

La solución general es:

Si se imponen estas condiciones iniciales: t=0, x=0, v=v0, la solución queda así:

En este ejemplo, se van a implementar los distintos casos de un oscilador armónico amortiguado usando el patrón Estrategia (en la figura se muestra la aplicación del patrón para este ejemplo).

Page 19 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

package oscilador; /** * Esta interfaz es la estrategia abstracta del patr ón Estrategia. * */ public interface Fuerza { public double calcularPosicionX(double t); }

package oscilador; /** * FuerzaAmortiguadora es una estrategia concreta de l patrón Estrategia. */ public class FuerzaAmortiguadora implements Fuerza{ /** * Frecuencia del movimiento. (w^2 = w0^2 - gamm a^2) */ private double omega; /** * Coeficiente de amortiguación del movimiento. ( gamma = lambda/(2m) ) */ private double gamma; /** * Método para calcular la posición del oscilado r en el instante t. * * @param t double */ public double calcularPosicionX(double t) { return ( (1./omega) * Math.exp(-gamma * t) * Math.sin(omega * t) ); } /** * Constructor. * * @param omega0 double

Figura 9. Aplicación del patrón Estrategia al análisis de los osciladores amortiguados

Page 20 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

* @param gamma double */ public FuerzaAmortiguadora(double omega0, doubl e gamma) throws Exception { if ( (omega0 > 0) && (gamma > 0) ) { this.omega = Math.sqrt( (omega0 * omega 0) - (gamma * gamma)); this.gamma = gamma; } else throw new Exception("Revise las constan tes del movimiento"); } }

package oscilador; /** * FuerzaCritica es una estrategia concreta del patr ón Estrategia. */ public class FuerzaCritica implements Fuerza{ /** * Frecuencia del movimiento. (w^2 = w0^2 - gamm a^2) */ private double omega; /** * Método para calcular la posición del oscilado r en el instante t. * * @param t double */ public double calcularPosicionX(double t) { return ( t * Math.exp(-omega * t) ); } /** * Constructor. * * @param omega0 double */ public FuerzaCritica(double omega0) throws Exce ption { if ( (omega0 > 0) ) this.omega = omega0; else throw new Exception("Revise las constan tes del movimiento"); } }

package oscilador; /** * FuerzaSobreAmortiguadora es una estrategia concre ta del patrón Estrategia. */ public class FuerzaSobreAmortiguadora implements Fu erza{ /** * Coeficiente beta del movimiento (beta^2 = gam ma^2 - w0^2) */ private double beta; /** * Coeficiente de amortiguación del movimiento. ( gamma = lambda/(2m) ) */ private double gamma; /** * Método para calcular la posición del oscilado r en el instante t. * * @param t double */ public double calcularPosicionX(double t) { return ( (1./beta) * Math.exp(-gamma * t) * ( (Math.exp(beta * t) - (Math.exp(-beta * t)) )/2. ) ); } /** * Constructor. * * @param omega0 double * @param gamma double */ public FuerzaSobreAmortiguadora(double omega0, double gamma) throws Exception { if ( (omega0 > 0) && (gamma > 0) ) { this.beta = Math.sqrt( (gamma * gamma) - (omega0 * omega0) ); this.gamma = gamma; } else throw new Exception("Revise las constan tes del movimiento");

Page 21 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

} }

package oscilador; /** * OsciladorAmortiguado es el contexto del patrón Es trategia. */ public class OsciladorAmortiguado { /** * Frecuencia natural del oscilador.(omega0^2) = k/m */ private double omega0; /** * Coeficiente de amortiguamiento del movimiento . gamma = lambda/(2m) */ private double gamma; /** * Velocidad inicial en el tiempo t=0. */ private double v0; /** * Tipo de fuerza. */ private Fuerza fuerza; /** * Método set para fuerza. * * @param fuerza Fuerza */ public void setFuerza(Fuerza fuerza) { this.fuerza = fuerza; } /** * Método para calcular la posición del oscilado r en el instante t. * * @param t double */ public double calcularPosicionX(double t) { return(v0 * fuerza.calcularPosicionX(t)); } /** * Constructor * * @param omega0 double * @param gamma double * @param fuerza Fuerza */ public OsciladorAmortiguado(double omega0, doub le gamma, double v0) throws Exception { this.omega0 = omega0; this.gamma = gamma; this.v0 = v0; if (gamma < omega0) setFuerza(new FuerzaAmortiguadora(omega 0, gamma)); else if (gamma == omega0) setFuerza (new FuerzaCritica(omega0)); else if (gamma > omega0) setFuerza (new FuerzaSobreAmortiguadora (omega0, gamma)); else throw new Exception("Debe elegir obliga toriamente un tipo de oscilador amortiguado"); } // Ejemplo del funcionamiento del programa public static void main (String args[]) throws Exception { // Oscilador sobreamortiguado OsciladorAmortiguado oa1 = new OsciladorAmo rtiguado(0.1, 0.2, 1.1); System.out.println(oa1.calcularPosicionX(5. )); // Posición a los cinco segundos // Oscilador amortiguado OsciladorAmortiguado oa2 = new OsciladorAmo rtiguado(0.2, 0.1, 1.1); System.out.println(oa2.calcularPosicionX(2. )); // Posición a los dos segundos // Oscilador crítico OsciladorAmortiguado oa3 = new OsciladorAmo rtiguado(0.2, 0.2, 1.1); System.out.println(oa3.calcularPosicionX(3. )); // Posición a los tres segundos } }

6. Ventajas y desventajas del patrón Estrategia.

Page 22 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

El patrón Estrategia proporciona cuatro grandes ventajas:

� Permite definir familias de algoritmos relacionados, lo que posibilita agrupar funciones comunes y facilitar la reutilización del código.

� Es una cómoda alternativa a la subclasificación. Para compartir comportamientos suelen generarse subclases y redefinir los métodos comunes. Este patrón permite que el comportamiento cambie dinámicamente, en tiempo de ejecución.

� Elimina el uso de sentencias condicionales (if, switch/case), cuyo abuso hace difícil de leer el código. En lugar de comprobar dinámicamente que comportamiento hay que elegir, la elección se hace cuando se crea un objeto Estrategia.

� Permite al cliente elegir entre diversas implementaciones de una misma operación.

Pese a que [5] señala explícitamente como ventaja del patrón Estrategia la eliminación de sentencias condicionales, creo que esa ventaja se debe al polimorfismo, no al patrón Estrategia.

Como no podía ser de otra manera, el patrón Estrategia presenta inconvenientes. Los dos más importantes son éstos:

� Incrementa el número de objetos que pueden ejecutarse en una aplicación.

� Puede sobrecargar innecesariamente la aplicación si algunas implementaciones de los algoritmos no necesitan todos los argumentos necesitados por una EstrategiaAbstracta. Un método de la estrategia abstracta puede declarar muchos argumentos innecesarios para casi todas las estrategias concretas, que habrá que pasar cada vez que se escoja una estrategia concreta, sean usados o no por ella. Por ejemplo, considérese un método hacerAlgo(ArrayList al, HashMap hm, HashSet hs) de una estrategia abstracta. Por estar declarado en la estrategia abstracta, todas las estrategias concretas deberán implementarlo y deberán recibir los argumentos al, hm y hs, aun cuando sólo usen uno de ellos. Como dentro de una colección puede haber cientos o miles de objetos, puede generarse un tráfico de datos innecesario. Una manera de paliar el paso de información innecesaria es aumentar el acoplamiento entre las estrategias y el contexto, de manera que el segundo delegue en las primeras.

� Los clientes deben conocer la existencia de las distintas estrategias. Si los clientes ignoran las estrategias o les resultan indiferentes, el uso del patrón resulta inapropiado.

Recursos

[1] Working with Objects. The OORam Software Engineering Method, Trygve Reenskaug , Prentice Hall , ISBN 0134529308

[2] A Pattern Definition - Software Patterns, http://hillside.net/patterns/definition.html

[3] A Pattern Language : Towns, Buildings, Construction, Christopher Alexander, Sara Ishikawa y Murray Silverstein , Oxford University Press , ISBN 0195019199

[4] Pattern-Oriented Software Architecture, Volume 1: A System of Patterns, Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Sommerlad, Michael Stal, Peter Sommerlad y Michael Stal , John Wiley & Sons , ISBN 0471958697

[5] Design Patterns: Elements of Reusable Object-Oriented Software, Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides , Addison-Wesley Pub Co , ISBN 0201633612

[6] Artículos sobre ingeniería del software en javaHispano, http://www.javahispano.org/articles.list.action?section=5

[7] Tutoriales sobre ingeniería del software en javaHispano, http://www.javahispano.org/tutorials.type.action?type=is

Acerca del autor

Miguel Ángel Abián Miguel Ángel Abián nació en Soria. Obtuvo la suficiencia investigadora en el Dpto. de Física Aplicada de la Universidad de Valencia con una tesina sobre electromagnetismo. Realizó varios cursos de doctorado relacionados con electromagnetismo, electrónica, semiconductores y cristales fotónicos. Ha recibido becas del IMPIVA (Instituto de la Mediana y Pequeña Industria Valenciana) y de la Universidad Politécnica de Valencia. Cursó un Máster estadounidense en UML y Java y otro sobre tecnologías de Internet/Intranet. Se incorporó en 1998 a AIDIMA, donde ha participado como investigador en 24 proyectos de investigación nacionales e internacionales relacionados con la Web semántica, tecnologías de la información, madera en construcción, biosensórica, bioelectrónica, telecomunicaciones, visión artificial; así como en la Red de Excelencia de la Comisión Europea INTEROP 2003-2007. Algunos de los proyectos europeos relacionados con las tecnologías semánticas en los que ha participado son ATHENA y STASIS (http://www.stasis-project.net/). El año 2006 estuvo cuatro meses como investigador invitado en el departamento Lehrstuhl für Messsystem und Sensortechnik de la Universidad Politécnica de Munich (TUM), donde colaboró en el desarrollo de nuevos métodos para la detección de defectos en superficies acabadas y en el diseño e implementación de sistemas distribuidos de sensores para el sector del automóvil y de energías renovables. En 2007 recibió un premio BANCAJA-UPV por un proyecto relacionado con la calidad interna de la madera. En 2009 recibió el premio internacional Schweighofer Innovation Prize -el premio más prestigioso en el sector forestal y de la madera- por su aportación al desarrollo de nuevas tecnologías de evaluación no destructiva de la madera en construcción. Actualmente es Responsable del Departamento de Tecnología y Biotecnología de la Madera y del Área de Construcción de Madera. Es coautor de 7 libros y guías técnicas relacionadas con el uso de la madera en la construcción y la visión artificial. También ha publicado varios artículos científicos en revistas como IEEE Transactions on Microwave Theory and Techniques y Wood Science and Technology. Ha participado como ponente en congresos y conferencias como European Congress on Computational Methods in Applied Sciences and Engineering, IEEE International Conference on Multisensor Fusion and Integration for Intelligent Systems, International Conference on Space Structures (IABSE-IASS) y en reuniones COST (European Cooperation in Science and Technology). Ha publicado más de 22 artículos técnicos en revistas sectoriales y técnicas. Es autor o coautor de 8 patentes, algunas de ellas en trámite. Tres de ellas corresponden a dispositivos y métodos para detectar la biodegradación de la madera en construcción. Actualmente, entre otros proyectos como SHBUILDINGS, WOODTECH, WOODRUB y CELLUWOOD, ha trabajado en SEMCONCEPT, un proyecto de I+D+i para aplicar tecnologías semánticas (ontologías, buscadores semánticos) en el diseño conceptual de productos industriales. Sus intereses actuales son la evolución de la programación orientada a objetos, Java, la Web semántica y sus tecnologías, la arquitectura orgánica, el surrealismo y París, siempre París.

Page 23 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...

Page 24 of 24El patrón Estrategia

21/05/2014file://F:\ArticuloEclipse\VersiónDefinitiva\Copia de patronE...