¿Se puede implementar el patrón de estrategia sin una ramificación significativa?

12

El patrón de estrategia funciona bien para evitar grandes construcciones si ... más y facilita la adición o el reemplazo de la funcionalidad. Sin embargo, todavía deja un defecto en mi opinión. Parece que en cada implementación todavía debe haber una construcción de ramificación. Puede ser una fábrica o un archivo de datos. Como ejemplo, tome un sistema de pedido.

Fábrica:

// All of these classes implement OrderStrategy
switch (orderType) {
case NEW_ORDER: return new NewOrder();
case CANCELLATION: return new Cancellation();
case RETURN: return new Return();
}

El código después de esto no debe preocuparse, y ahora solo hay un lugar para agregar un nuevo tipo de orden, pero esta sección de código aún no es extensible. Sacarlo en un archivo de datos ayuda en algo a la legibilidad (discutible, lo sé):

<strategies>
   <order type="NEW_ORDER">com.company.NewOrder</order>
   <order type="CANCELLATION">com.company.Cancellation</order>
   <order type="RETURN">com.company.Return</order>
</strategies>

Pero esto aún agrega código repetitivo para procesar el archivo de datos: se otorga, es más fácil de probar y es un código relativamente estable, pero a pesar de ello, ofrece una complejidad adicional.

Además, este tipo de construcción no prueba bien la integración. Cada estrategia individual puede ser más fácil de probar ahora, pero cada nueva estrategia que agregue es una complejidad adicional para probar. Es menos de lo que tendría si no hubiera usado el patrón, pero todavía está allí.

¿Hay una manera de implementar el patrón de estrategia que mitiga esta complejidad? ¿O es esto tan simple como parece, y tratar de ir más allá solo agregaría otra capa de abstracción para poco o ningún beneficio?

    
pregunta Michael K 01.05.2012 - 20:57

7 respuestas

15

Por supuesto que no. Incluso si usa un contenedor de IoC, tendrá que tener condiciones en algún lugar, para decidir qué implementación concreta inyectar. Esta es la naturaleza del patrón de Estrategia.

Realmente no veo por qué la gente piensa que esto es un problema. Hay declaraciones en algunos libros, como Refactoring de Fowler , que si ve un interruptor / caja o una cadena de En el medio de otro código, debe considerar un olor y buscar para moverlo a su propio método. Si el código dentro de cada caso es más que una línea, quizás dos, entonces debería considerar convertir ese método en un Método de Fábrica, devolviendo Estrategias.

Algunas personas han tomado esto para decir que un cambio / caso es malo. Este no es el caso. Pero debería residir por sí solo, si es posible.

    
respondido por el pdr 01.05.2012 - 21:15
10
  

¿Se puede implementar el patrón de estrategia sin una ramificación significativa?

Sí , utilizando un hashmap / diccionario donde se registra cada implementación de Estrategia. El método de fábrica se convertirá en algo así como

Class strategyType = allStrategies[orderType];
return runtime.create(strategyType);

Cada implementación de estrategia debe registrar la fábrica con su orderType y cierta información sobre cómo crear una clase.

factory.register(NEW_ORDER, NewOrder.class);

Puede usar el constructor estático para el registro, si su idioma lo admite.

El método de registro no hace nada más que agregar un nuevo valor al mapa de hash:

void register(OrderType orderType, Class class)
{
   allStrategies[orderType] = class;
}

[actualización 2012-05-04]
Esta solución es mucho más compleja que la "solución de conmutación" original que preferiría la mayor parte del tiempo.

Sin embargo, en un entorno donde las estrategias cambian con frecuencia (es decir, el cálculo del precio según el cliente, el tiempo, ...), esta solución de mapa hash combinada con un contenedor IoC podría ser una buena solución.

    
respondido por el k3b 02.05.2012 - 10:48
2

"Estrategia" se trata de tener que elegir entre algoritmos alternativos una vez , no más, pero tampoco menos. En algún lugar de su programa, alguien tiene que tomar una decisión, tal vez el usuario o su programa. Si utiliza una IoC, una reflexión, un evaluador de archivos de datos o un constructor de conmutación / caso para implementar esto no cambiará la situación.

    
respondido por el Doc Brown 02.05.2012 - 08:20
1

El patrón de estrategia se usa cuando se especifican posibles comportamientos y se usa mejor cuando se designa un controlador al inicio. La especificación de la instancia de la estrategia a utilizar se puede realizar a través de un Mediador, su contenedor de IoC estándar, algunos de los que Factory describe, o simplemente utilizando el correcto en función del contexto (ya que a menudo, la estrategia se proporciona como parte de un uso más amplio de la clase que lo contiene).

Para este tipo de comportamiento donde se deben llamar diferentes métodos basados en datos, entonces sugeriría usar las construcciones de polimorfismo suministradas por el lenguaje en cuestión; no el patrón de estrategia.

    
respondido por el Telastyn 01.05.2012 - 21:16
1

Al hablar con otros desarrolladores donde trabajo, encontré otra solución interesante, específica para Java, pero estoy seguro de que la idea funciona en otros idiomas. Construya una enumeración (o un mapa, no importa mucho) utilizando las referencias de clase y ejemplifique utilizando la reflexión.

enum FactoryType {
   Type1(Type1.class),
   Type2(Type2.class);

   private Class<? extends Type> clazz;

   private FactoryType(Class<? extends Type> clazz) {
      this.clazz = clazz;
   }

   public Class<? extends Type> getTypeClass() {
      return clazz;
   }
}

Esto reduce drásticamente el código de fábrica:

public Type create(FactoryType type) throws Exception {
   return type.getTypeClass().newInstance();
}

(Ignore el mal manejo de errores, código de ejemplo :))

No es el más flexible, ya que aún se requiere una compilación ... pero reduce el cambio de código a una línea. Me gusta que los datos estén separados del código de fábrica. Incluso puede hacer cosas como establecer parámetros mediante el uso de interfaces / clases abstractas para proporcionar un método común o crear anotaciones en tiempo de compilación para forzar firmas de constructores específicos.

    
respondido por el Michael K 27.07.2012 - 16:24
1

La extensibilidad se puede mejorar haciendo que cada clase defina su propio tipo de orden. Luego, su fábrica selecciona la que coincide.

Por ejemplo:

public interface IOrderStrategy
{
    OrderType OrderType { get; }
}

public class NewOrder : IOrderStrategy
{
    public OrderType OrderType { get; } = OrderType.NewOrder;
}

public class OrderFactory
{
    private IEnumerable<IOrderStrategy> _strategies;

    public OrderFactory(IEnumerable<IOrderStrategy> strategies) // Injected by IoC container
    {
        _strategies = strategies;
    }

    public IOrderStrategy Create(OrderType orderType)
    {
        IOrderStrategy strategy = _strategies.FirstOrDefault(s => s.OrderType == orderType);

        if (strategy == null)
            throw new ArgumentException("Invalid order type.", nameof(orderType));

        return strategy;
    }
}
    
respondido por el Eric Eskildsen 21.02.2017 - 13:35
0

Creo que debería haber un límite en la cantidad de sucursales que son aceptables.

Por ejemplo, si tengo más de ocho casos dentro de mi declaración de cambio, luego reevalúo mi código y busco lo que puedo re-factorizar. Muy a menudo encuentro que ciertos casos se pueden agrupar en una fábrica separada. Mi suposición aquí es que hay una fábrica que construye estrategias.

De cualquier manera, no puede evitar esto y en algún lugar tendrá que verificar el estado o tipo del objeto con el que está trabajando. Entonces construirás una estrategia para eso. Buena vieja separación de preocupaciones.

    
respondido por el CodeART 01.05.2012 - 21:18

Lea otras preguntas en las etiquetas