Design patterns
Patrón de estrategia
Buscar..
Ocultar los detalles de la implementación de la estrategia.
Una guía muy común en el diseño orientado a objetos es "lo menos posible pero lo necesario". Esto también se aplica al patrón de estrategia: por lo general, es recomendable ocultar los detalles de la implementación, por ejemplo, qué clases implementan las estrategias.
Para estrategias simples que no dependen de parámetros externos, el enfoque más común es hacer que la clase implementadora sea privada (clases anidadas) o paquete-privada y exponga una instancia a través de un campo estático de una clase pública:
public class Animal {
private static class AgeComparator implements Comparator<Animal> {
public int compare(Animal a, Animal b) {
return a.age() - b.age();
}
}
// Note that this field has the generic type Comparator<Animal>, *not*
// Animal.AgeComparator!
public static final Comparator<Animal> AGE_COMPARATOR = new AgeComparator();
private final int age;
Animal(int age) {
this.age = age;
}
public int age() {
return age;
}
}
List<Animal> myList = new LinkedList<>();
myList.add(new Animal(10));
myList.add(new Animal(5));
myList.add(new Animal(7));
myList.add(new Animal(9));
Collections.sort(
myList,
Animal.AGE_COMPARATOR
);
El campo público Animal.AGE_COMPARATOR
define una estrategia que luego se puede usar en métodos como Collections.sort
, pero no requiere que el usuario sepa nada sobre su implementación, ni siquiera la clase implementadora.
Si lo prefieres, puedes usar una clase anónima:
public class Animal {
public static final Comparator<Animal> AGE_COMPARATOR = new Comparator<Animal> {
public int compare(Animal a, Animal b) {
return a.age() - b.age();
}
};
// other members...
}
Si la estrategia es un poco más compleja y requiere parámetros, es muy común usar métodos de fábrica estáticos como Collections.reverseOrder(Comparator<T>)
. El tipo de retorno del método no debe exponer ningún detalle de implementación, por ejemplo, reverseOrder()
se implementa como
public static <T> Comparator<T> reverseOrder(Comparator<T> cmp) {
// (Irrelevant lines left out.)
return new ReverseComparator2<>(cmp);
}
Ejemplo de patrón de estrategia en java con clase de contexto
Estrategia:
Strategy
es un patrón de comportamiento, que permite cambiar el algoritmo dinámicamente de una familia de algoritmos relacionados.
UML de patrón de estrategia de Wikipedia
import java.util.*;
/* Interface for Strategy */
interface OfferStrategy {
public String getName();
public double getDiscountPercentage();
}
/* Concrete implementation of base Strategy */
class NoDiscountStrategy implements OfferStrategy{
public String getName(){
return this.getClass().getName();
}
public double getDiscountPercentage(){
return 0;
}
}
/* Concrete implementation of base Strategy */
class QuarterDiscountStrategy implements OfferStrategy{
public String getName(){
return this.getClass().getName();
}
public double getDiscountPercentage(){
return 0.25;
}
}
/* Context is optional. But if it is present, it acts as single point of contact
for client.
Multiple uses of Context
1. It can populate data to execute an operation of strategy
2. It can take independent decision on Strategy creation.
3. In absence of Context, client should be aware of concrete strategies. Context acts a wrapper and hides internals
4. Code re-factoring will become easy
*/
class StrategyContext {
double price; // price for some item or air ticket etc.
Map<String,OfferStrategy> strategyContext = new HashMap<String,OfferStrategy>();
StrategyContext(double price){
this.price= price;
strategyContext.put(NoDiscountStrategy.class.getName(),new NoDiscountStrategy());
strategyContext.put(QuarterDiscountStrategy.class.getName(),new QuarterDiscountStrategy());
}
public void applyStrategy(OfferStrategy strategy){
/*
Currently applyStrategy has simple implementation. You can Context for populating some more information,
which is required to call a particular operation
*/
System.out.println("Price before offer :"+price);
double finalPrice = price - (price*strategy.getDiscountPercentage());
System.out.println("Price after offer:"+finalPrice);
}
public OfferStrategy getStrategy(int monthNo){
/*
In absence of this Context method, client has to import relevant concrete Strategies everywhere.
Context acts as single point of contact for the Client to get relevant Strategy
*/
if ( monthNo < 6 ) {
return strategyContext.get(NoDiscountStrategy.class.getName());
}else{
return strategyContext.get(QuarterDiscountStrategy.class.getName());
}
}
}
public class StrategyDemo{
public static void main(String args[]){
StrategyContext context = new StrategyContext(100);
System.out.println("Enter month number between 1 and 12");
int month = Integer.parseInt(args[0]);
System.out.println("Month ="+month);
OfferStrategy strategy = context.getStrategy(month);
context.applyStrategy(strategy);
}
}
salida:
Enter month number between 1 and 12
Month =1
Price before offer :100.0
Price after offer:100.0
Enter month number between 1 and 12
Month =7
Price before offer :100.0
Price after offer:75.0
Declaración de problema: Ofrezca un descuento del 25% en el precio del artículo para los meses de julio a diciembre. No ofrezca ningún descuento por los meses de enero-junio.
El ejemplo anterior muestra el uso del patrón de Strategy
con Context
. Context
se puede utilizar como un único punto de contacto para el Client
.
Dos estrategias: NoOfferStrategy
y QuarterDiscountStrategy
se han declarado según la declaración del problema.
Como se muestra en la columna de salida, obtendrá un descuento dependiendo del mes que haya ingresado
Caso (s) de uso para el patrón de estrategia :
Use este patrón cuando tenga una familia de algoritmos intercambiables y tenga que cambiar el algoritmo en tiempo de ejecución.
Mantenga el código limpio eliminando las declaraciones condicionales
Patrón de estrategia sin una clase de contexto / Java
El siguiente es un ejemplo simple de usar el patrón de estrategia sin una clase de contexto. Existen dos estrategias de implementación que implementan la interfaz y resuelven el mismo problema de diferentes maneras. Los usuarios de la clase de traducción en inglés pueden llamar al método de traducción y elegir qué estrategia les gustaría usar para la traducción, especificando la estrategia deseada.
// The strategy interface
public interface TranslationStrategy {
String translate(String phrase);
}
// American strategy implementation
public class AmericanTranslationStrategy implements TranslationStrategy {
@Override
public String translate(String phrase) {
return phrase + ", bro";
}
}
// Australian strategy implementation
public class AustralianTranslationStrategy implements TranslationStrategy {
@Override
public String translate(String phrase) {
return phrase + ", mate";
}
}
// The main class which exposes a translate method
public class EnglishTranslation {
// translate a phrase using a given strategy
public static String translate(String phrase, TranslationStrategy strategy) {
return strategy.translate(phrase);
}
// example usage
public static void main(String[] args) {
// translate a phrase using the AustralianTranslationStrategy class
String aussieHello = translate("Hello", new AustralianTranslationStrategy());
// Hello, mate
// translate a phrase using the AmericanTranslationStrategy class
String usaHello = translate("Hello", new AmericanTranslationStrategy());
// Hello, bro
}
}
Usando interfaces funcionales de Java 8 para implementar el patrón de Estrategia
El propósito de este ejemplo es mostrar cómo podemos realizar el patrón de estrategia utilizando las interfaces funcionales de Java 8. Comenzaremos con un simple uso de códigos de casos en Java clásico, y luego lo recodificaremos en la forma de Java 8.
El problema de ejemplo que usamos es una familia de algoritmos (estrategias) que describen diferentes formas de comunicación a distancia.
La versión clásica de Java
El contrato para nuestra familia de algoritmos se define mediante la siguiente interfaz:
public interface CommunicateInterface {
public String communicate(String destination);
}
Luego podemos implementar una serie de algoritmos, de la siguiente manera:
public class CommunicateViaPhone implements CommunicateInterface {
@Override
public String communicate(String destination) {
return "communicating " + destination +" via Phone..";
}
}
public class CommunicateViaEmail implements CommunicateInterface {
@Override
public String communicate(String destination) {
return "communicating " + destination + " via Email..";
}
}
public class CommunicateViaVideo implements CommunicateInterface {
@Override
public String communicate(String destination) {
return "communicating " + destination + " via Video..";
}
}
Estos pueden ser instanciados de la siguiente manera:
CommunicateViaPhone communicateViaPhone = new CommunicateViaPhone();
CommunicateViaEmail communicateViaEmail = new CommunicateViaEmail();
CommunicateViaVideo communicateViaVideo = new CommunicateViaVideo();
A continuación, implementamos un servicio que utiliza la estrategia:
public class CommunicationService {
private CommunicateInterface communcationMeans;
public void setCommuncationMeans(CommunicateInterface communcationMeans) {
this.communcationMeans = communcationMeans;
}
public void communicate(String destination) {
this.communcationMeans.communicate(destination);
}
}
Finalmente, podemos utilizar las diferentes estrategias de la siguiente manera:
CommunicationService communicationService = new CommunicationService();
// via phone
communicationService.setCommuncationMeans(communicateViaPhone);
communicationService.communicate("1234567");
// via email
communicationService.setCommuncationMeans(communicateViaEmail);
communicationService.communicate("[email protected]");
Usando interfaces funcionales de Java 8
El contrato de las diferentes implementaciones de algoritmos no necesita una interfaz dedicada. En su lugar, podemos describirlo utilizando la interfaz java.util.function.Function<T, R>
.
Los diferentes algoritmos que componen the family of algorithms
se pueden expresar como expresiones lambda. Esto reemplaza las clases de estrategia y sus instancias.
Function<String, String> communicateViaEmail =
destination -> "communicating " + destination + " via Email..";
Function<String, String> communicateViaPhone =
destination -> "communicating " + destination + " via Phone..";
Function<String, String> communicateViaVideo =
destination -> "communicating " + destination + " via Video..";
A continuación, podemos codificar el "servicio" de la siguiente manera:
public class CommunicationService {
private Function<String, String> communcationMeans;
public void setCommuncationMeans(Function<String, String> communcationMeans) {
this.communcationMeans = communcationMeans;
}
public void communicate(String destination) {
this.communcationMeans.communicate(destination);
}
}
Finalmente utilizamos las siguientes estrategias.
CommunicationService communicationService = new CommunicationService();
// via phone
communicationService.setCommuncationMeans(communicateViaPhone);
communicationService.communicate("1234567");
// via email
communicationService.setCommuncationMeans(communicateViaEmail);
communicationService.communicate("[email protected]");
O incluso:
communicationService.setCommuncationMeans(
destination -> "communicating " + destination + " via Smoke signals.." );
CommunicationService.communicate("anyone");
Estrategia (PHP)
Ejemplo de www.phptherightway.com
<?php
interface OutputInterface
{
public function load();
}
class SerializedArrayOutput implements OutputInterface
{
public function load()
{
return serialize($arrayOfData);
}
}
class JsonStringOutput implements OutputInterface
{
public function load()
{
return json_encode($arrayOfData);
}
}
class ArrayOutput implements OutputInterface
{
public function load()
{
return $arrayOfData;
}
}