En la programación de computadoras , el patrón de estrategia (también conocido como patrón de política ) es un patrón de diseño de software de comportamiento que permite seleccionar un algoritmo en tiempo de ejecución. En lugar de implementar un solo algoritmo directamente, el código recibe instrucciones en tiempo de ejecución sobre cuál en una familia de algoritmos usar. [1]
La estrategia permite que el algoritmo varíe independientemente de los clientes que lo utilicen. [2] La estrategia es uno de los patrones incluidos en el influyente libro Design Patterns de Gamma et al. [3] que popularizó el concepto de usar patrones de diseño para describir cómo diseñar software orientado a objetos flexible y reutilizable. Aplazar la decisión sobre qué algoritmo utilizar hasta el tiempo de ejecución permite que el código de llamada sea más flexible y reutilizable.
Por ejemplo, una clase que realiza la validación de los datos entrantes puede utilizar el patrón de estrategia para seleccionar un algoritmo de validación según el tipo de datos, la fuente de los datos, la elección del usuario u otros factores discriminatorios. Estos factores no se conocen hasta el tiempo de ejecución y pueden requerir una validación radicalmente diferente. Los algoritmos de validación (estrategias), encapsulados por separado del objeto de validación, pueden ser utilizados por otros objetos de validación en diferentes áreas del sistema (o incluso en diferentes sistemas) sin duplicación de código .
Normalmente, el patrón de estrategia almacena una referencia a algún código en una estructura de datos y la recupera. Esto puede lograrse mediante mecanismos tales como el nativo de puntero de función , la función de primera clase , clases o instancias de clases en programación orientada a objetos idiomas, o el acceso a almacenamiento interno del código de la implementación del lenguaje a través de la reflexión .
Estructura
Diagrama de secuencia y clase UML
En el diagrama de clases de UML anterior , la Context
clase no implementa un algoritmo directamente. En cambio, se Context
refiere a la Strategy
interfaz para realizar un algoritmo ( strategy.algorithm()
), lo que lo hace Context
independiente de cómo se implementa un algoritmo. Las clases Strategy1
y Strategy2
implementan la Strategy
interfaz, es decir, implementan (encapsulan) un algoritmo.
El diagrama de secuencia UML muestra las interacciones en tiempo de ejecución: el Context
objeto delega un algoritmo a diferentes Strategy
objetos. Primero, Context
invoca algorithm()
un Strategy1
objeto, que realiza el algoritmo y devuelve el resultado a Context
. A partir de entonces, Context
cambia su estrategia y llama algorithm()
a un Strategy2
objeto, que realiza el algoritmo y devuelve el resultado a Context
.
Diagrama de clase
Ejemplo
C#
El siguiente ejemplo está en C # .
public class StrategyPatternWiki { public static void Main ( String [] args ) { // Preparar estrategias var normalStrategy = new NormalStrategy (); var happyHourStrategy = new HappyHourStrategy (); var firstCustomer = new CustomerBill ( normalStrategy ); // Facturación normal firstCustomer . Agregar ( 1.0 , 1 ); // Inicie Happy Hour firstCustomer . Estrategia = happyHourStrategy ; firstCustomer . Agregar ( 1.0 , 2 ); // Nuevo cliente CustomerBill secondCustomer = new CustomerBill ( happyHourStrategy ); secondCustomer . Agregar ( 0.8 , 1 ); // El cliente paga firstCustomer . Imprimir (); // Finalizar la Happy Hour secondCustomer . Estrategia = normalStrategy ; secondCustomer . Agregar ( 1.3 , 2 ); secondCustomer . Agregar ( 2.5 , 1 ); secondCustomer . Imprimir (); } }// CustomerBill como nombre de clase, ya que pertenece estrictamente a la clase de factura de un cliente CustomerBill { private IList < double > drinks ; // Obtener / Establecer estrategia public IBillingStrategy Strategy { get ; establecer ; } CustomerBill público ( estrategia IBillingStrategy ) { this . bebidas = nueva Lista < doble > (); esto . Estrategia = estrategia ; } public void Add ( precio doble , cantidad int ) { this . bebidas . Agregar ( esto . Estrategia . GetActPrice ( precio * cantidad )); } // Pago de factura pública nula Print () { doble suma = 0 ; foreach ( var drinkCost en esto . bebidas ) { suma + = drinkCost ; } Consola . WriteLine ( $ "Total adeudado: {suma}." ); esto . bebidas . Borrar (); } }interfaz IBillingStrategy { doble GetActPrice ( doble rawPrice ); }// Estrategia de facturación normal (precio sin cambios) class NormalStrategy : IBillingStrategy { public double GetActPrice ( double rawPrice ) => rawPrice ; }// Estrategia para Happy hour (50% de descuento) class HappyHourStrategy : IBillingStrategy { public double GetActPrice ( double rawPrice ) => rawPrice * 0.5 ; }
Java
El siguiente ejemplo está en Java .
import java.util.ArrayList ; import java.util.List ;interface BillingStrategy { // Utilice un precio en centavos para evitar el error de redondeo de punto flotante int getActPrice ( int rawPrice ); // Estrategia de facturación normal (precio sin cambios) static BillingStrategy normalStrategy () { return rawPrice -> rawPrice ; } // Estrategia para la hora feliz (50% de descuento) static BillingStrategy happyHourStrategy () { return rawPrice -> rawPrice / 2 ; } }class CustomerBill { Lista final privada < Integer > bebidas = new ArrayList <> (); estrategia de BillingStrategy privada ; pública CustomerBill ( BillingStrategy estrategia ) { esto . estrategia = estrategia ; } public void add ( int precio , int cantidad ) { this . bebidas . agregar ( esta . estrategia . getActPrice ( precio * cantidad )); } // Pago de la factura public void print () { int sum = this . bebidas . corriente (). mapToInt ( v -> v ). suma (); Sistema . fuera . println ( "Total adeudado:" + suma ); esto . bebidas . claro (); } // Establecer estrategia public void setStrategy ( estrategia BillingStrategy ) { this . estrategia = estrategia ; } } public class StrategyPattern { public static void main ( String [] argumentos ) { // Preparar estrategias BillingStrategy normalStrategy = BillingStrategy . normalStrategy (); BillingStrategy happyHourStrategy = BillingStrategy . happyHourStrategy (); CustomerBill firstCustomer = new CustomerBill ( normalStrategy ); // Facturación normal firstCustomer . sumar ( 100 , 1 ); // Inicie Happy Hour firstCustomer . setStrategy ( happyHourStrategy ); firstCustomer . sumar ( 100 , 2 ); // Nuevo cliente CustomerBill secondCustomer = new CustomerBill ( happyHourStrategy ); secondCustomer . agregar ( 80 , 1 ); // El cliente paga firstCustomer . imprimir (); // Finalizar la Happy Hour secondCustomer . setStrategy ( normalStrategy ); secondCustomer . sumar ( 130 , 2 ); secondCustomer . sumar ( 250 , 1 ); secondCustomer . imprimir (); } }
Estrategia y principio abierto / cerrado
Según el patrón de estrategia, los comportamientos de una clase no deben heredarse. En su lugar, deben encapsularse mediante interfaces. Esto es compatible con el principio abierto / cerrado (OCP), que propone que las clases deben estar abiertas para extensión pero cerradas para modificación.
Como ejemplo, considere una clase de automóvil. Dos posibles funcionalidades para el coche son frenar y acelerar . Dado que los comportamientos de aceleración y frenado cambian con frecuencia entre modelos, un enfoque común es implementar estos comportamientos en subclases. Este enfoque tiene inconvenientes importantes: los comportamientos de aceleración y frenado deben declararse en cada nuevo modelo de automóvil. El trabajo de administrar estos comportamientos aumenta enormemente a medida que aumenta el número de modelos y requiere que el código se duplique en todos los modelos. Además, no es fácil determinar la naturaleza exacta del comportamiento de cada modelo sin investigar el código en cada uno.
El patrón de estrategia usa composición en lugar de herencia . En el patrón de estrategia, los comportamientos se definen como interfaces separadas y clases específicas que implementan estas interfaces. Esto permite un mejor desacoplamiento entre el comportamiento y la clase que usa el comportamiento. El comportamiento se puede cambiar sin romper las clases que lo usan, y las clases pueden cambiar entre comportamientos cambiando la implementación específica utilizada sin requerir ningún cambio de código significativo. Los comportamientos también se pueden cambiar en tiempo de ejecución, así como en tiempo de diseño. Por ejemplo, el comportamiento del freno de un objeto de automóvil se puede cambiar de BrakeWithABS () a Brake () cambiando el miembro brakeBehavior a:
BrakeBehavior = nuevo freno ();
/ * Familia encapsulada de algoritmos * Interfaz y sus implementaciones * / interfaz pública IBrakeBehavior { public void brake (); } La clase pública BrakeWithABS implementa IBrakeBehavior { public void brake () { System . fuera . println ( "Freno con ABS aplicado" ); } }public class Brake implementa IBrakeBehavior { public void brake () { System . fuera . println ( "Freno simple aplicado" ); } }/ * Cliente que puede usar los algoritmos anteriores indistintamente * / public abstract class Car { private IBrakeBehavior brakeBehavior ; coche público ( IBrakeBehavior brakeBehavior ) { this . BrakeBehavior = BrakeBehavior ; } public void applyBrake () { BrakeBehavior . freno (); } public void setBrakeBehavior ( IBrakeBehavior brakeType ) { this . BrakeBehavior = brakeType ; } }/ * Cliente 1 usa un algoritmo (Freno) en el constructor * / clase pública Sedan extiende Coche { public Sedan () { super ( new Brake ()); } } / * El cliente 2 usa otro algoritmo (BrakeWithABS) en el constructor * / public class SUV extiende Car { public SUV () { super ( new BrakeWithABS ()); } }/ * Usando el ejemplo de Car * / public class CarExample { public static void main ( final String [] argumentos ) { Car sedanCar = new Sedan (); sedánCar . applyBrake (); // Esto invocará la clase "Brake" Coche suvCar = SUV nuevo (); suvCar . applyBrake (); // Esto invocará la clase "BrakeWithABS" // establece el comportamiento del freno dinámicamente suvCar . setBrakeBehavior ( nuevo freno () ); suvCar . applyBrake (); // Esto invocará la clase "Brake" } }
Ver también
Referencias
- ^ "El patrón de diseño de la estrategia: problema, solución y aplicabilidad" . w3sDesign.com . Consultado el 12 de agosto de 2017 .
- ^ Eric Freeman, Elisabeth Freeman, Kathy Sierra y Bert Bates, Head First Design Patterns , primera edición, capítulo 1, página 24, O'Reilly Media, Inc, 2004. ISBN 978-0-596-00712-6
- ^ Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (1994). Patrones de diseño: elementos de software orientado a objetos reutilizable . Addison Wesley. págs. 315ff . ISBN 0-201-63361-2.CS1 maint: varios nombres: lista de autores ( enlace )
- ^ "El patrón de diseño de la estrategia - Estructura y colaboración" . w3sDesign.com . Consultado el 12 de agosto de 2017 .
- ^ http://www.mcdonaldland.info/2007/11/28/40/
enlaces externos
- Patrón de estrategia en UML (en español)
- Geary, David (26 de abril de 2002). "Estrategia para el éxito" . Patrones de diseño de Java. JavaWorld . Consultado el 20 de julio de 2020 .
- Patrón de estrategia para el artículo C
- Refactorización: reemplace el código de tipo con estado / estrategia
- El patrón de diseño de estrategia en la Wayback Machine (archivado el 15 de abril de 2017) Implementación del patrón de estrategia en JavaScript