If Else - Código lógico repetido

15

Mi jefe me dio un proyecto con una lógica particular. Tengo que desarrollar una página web que tenga que guiar al navegador a través de muchos casos hasta que llegue al producto.

Este es el esquema de ruta de navegación en el sitio:

Esquema de ruta

¡IMPORTANTE!

En la página Productos, el navegador puede elegir qué filtro quiere.

  • Si A, él / ella DEBE pasar por B (y luego C, por supuesto) o C y llegar a los productos.
  • Si B, él / ella DEBE pasar por la C y alcanzar los productos.
  • Si C, él / ella llega directamente a los productos.

Por supuesto, si empiezo desde AI sigo el camino más largo y cuando llego a mis productos tengo 3 filtros activos.

Hasta ahora desarrollé el siguiente código que funciona bien.

if filter_A
  if filter_B
     filter_C()
     .. else ..
  else
     filter_C
    .. else ..
else
   if filter_B
      filter_C()
     .. else ..
   else
     filter_C()
     .. else ..

Estoy aquí para preguntar qué habría hecho un programador más experto en esta situación. No respeté el principio DRY, no me gusta y me gustaría conocer una forma alternativa de desarrollar este tipo de lógica.

Pensé en dividir cada sección de código en funciones, pero ¿es una buena idea en este caso?

Kevin Cittadini
fuente
El diagrama de flujo de control muestra todo el control que pasa filter_C, pero las declaraciones condicionales indican que el flujo de control puede dar la vuelta filter_C. Es filter_Copcional?
CurtisHx
@CurtisHx Filter C es obligatorio. Sí, lo siento, mi error hice copiar y pegar.
Kevin Cittadini
2
¿Cómo puede esta pregunta ser independiente del lenguaje ? Una solución idiomática en Java sería muy diferente de una solución idiomática en Haskell. ¿No has decidido un idioma para tu proyecto?
200_success

Respuestas:

20

No ha dicho si los filtros toman algún parámetro. Por ejemplo, filter_Apodría ser un filtro de categoría, por lo que no es solo una cuestión de "¿Necesito aplicar? filter_A", Podría ser "Necesito aplicar filter_Ay devolver todos los registros con el campo de categoría = fooCategory".

La forma más sencilla de implementar exactamente lo que ha descrito (pero asegúrese de leer la segunda mitad de la respuesta a continuación) es similar a las otras respuestas, pero no tendría ninguna verificación booleana. Yo definiría interfaces: FilterA, FilterB, FilterC. Entonces puedes tener algo como (soy un programador de Java, así que esta será una sintaxis de Java-esque):

class RequestFilters {
    FilterA filterA;
    FilterB filterB;
    FilterC filterC;
}

Entonces podría tener algo como esto (usando el patrón enton singleton de Effective Java ):

enum NoOpFilterA implements FilterA {
    INSTANCE;

    public List<Item> applyFilter(List<Item> input) {
       return input;
    }
}

Pero si realmente desea que se filtren algunos elementos, puede proporcionar una instancia de una FilterAimplementación que realmente haga algo. Su método de filtración será muy simple.

List<Item> filterItems(List<Item> data, RequestFilters filters) {
    List<Item> returnedList = data;
    returnedList = filters.filterA.filter(data);
    returnedList = filters.filterB.filter(data);
    returnedList = filters.filterC.filter(data);
    return returnedList;
}

Pero recién estoy comenzando.

Sospecho que la applyFilterllamada en realidad será bastante similar para los tres tipos de filtros. Si ese es el caso, ni siquiera lo haría de la manera descrita anteriormente. Puede obtener un código aún más limpio con solo una interfaz y luego hacer esto:

class ChainedFilter implements Filter {
     List<Filter> filterList;

     void addFilter(Filter filter) {
          filterList.add(filter);
     }

     List<Item> applyFilter(List<Item> input) {
         List<Item> returnedList = input;
         for(Filter f : filterList) {
             returnedList = f.applyFilter(returnedList);
         }
         return returnedList;
     }
}

Luego, a medida que su usuario navega por las páginas, simplemente agrega una nueva instancia de cualquier filtro que necesite cuando sea apropiado. Esto le permitirá poder aplicar múltiples instancias del mismo filtro con diferentes argumentos si necesita ese comportamiento en el futuro, y también agregar filtros adicionales en el futuro sin tener que cambiar su diseño .

Además, puede agregar algo como lo NoOpFilteranterior o simplemente no puede agregar un filtro en particular a la lista, lo que sea más fácil para su código.

durron597
fuente
Gracias, porque encuentra la forma más sencilla posible de cambiar la lógica sin cambiar también el código. Esto hace que su respuesta sea la mejor. Implementaré este diseño de código lo antes posible
Kevin Cittadini
Si tuviera su Filtercomo Predicateentonces, podría usarlo directamente en la StreamAPI. Muchos lenguajes tienen construcciones funcionales similares.
Boris the Spider
3
@BoristheSpider Eso solo si está usando Java 8; Ni siquiera dijo qué idioma estaba usando. Otros idiomas tienen tal construcción, pero no quería profundizar en los diferentes sabores de cómo hacerlo
Durron597
3
Entendido: vale la pena mencionar que es una vía para explorar si el OP quiere proporcionar la implementación más limpia posible. Ciertamente tienes mi +1 para una respuesta excelente.
Boris the Spider
3

En este caso, es importante separar la lógica del filtrado y el flujo de control de cómo funcionan los filtros. La lógica del filtro debe separarse en funciones individuales, que pueden ejecutarse de forma independiente entre sí.

ApplyFilterA();
ApplyFilterB();
ApplyFilterC();

En el código de ejemplo publicado, hay 3 booleanos filter_A, filter_By filter_C. Sin embargo, desde el diagrama, filter_Csiempre se ejecuta, por lo que se puede cambiar a un incondicional.

NOTA: Supongo que el diagrama de flujo de control es correcto. Existe una discrepancia entre el código de muestra publicado y el diagrama de flujo de control.

Un código separado controla qué filtros se ejecutan

ApplyFilters(bool filter_A, bool filter_B)
{
    listOfProducts tmp;
    if (filter_A)
        ApplyFilterA();
    if (filter_B)
        ApplyFilterB();
    ApplyFilterC();
}

Existe una separación clara entre controlar qué filtros se ejecutan y qué hacen los filtros. Romper esas dos piezas de lógica aparte.

CurtisHx
fuente
+1 Esto parece mucho más simple y desacoplado que la respuesta aceptada.
winkbrace
2

Supongo que quiere el algoritmo más simple y claro.
En este caso, sabiendo que el filtro c siempre se aplica, lo viviría de la lógica if y lo aplicaría al final independientemente. Como se ve en su diagrama de flujo, cada filtro antes de c es opcional, porque cada uno de ellos puede aplicarse o no. En este caso, viviría ifs separados de cada filtro, sin anidar y encadenar:

if filter_a
  do_filter_a()

if filter_b
  do_filter_b()

do_filter_c()

si tiene un diagrama de flujo con un número variable de filtros, antes del obligatorio, en su lugar, guardaría todos los filtros en una matriz, en el orden en que deberían aparecer. Luego, procese filtros opcionales en el bucle y aplique el obligatorio al final, fuera del bucle:

optional_filters_array = (a, b, c, d, e, f, g, h, etc)

for current_filter in optional_filters_array
  do_filter(current_filter)

do_required_filter()

o:

optional_filters_array = (a, b, c, d, e, f, g, h, etc)
required_filter = last_filter


for current_filter in optional_filters_array
  do_filter(current_filter)

do_filter(required_filter)

de origen, tendría que definir la subrutina de procesamiento de filtro.

igoryonya
fuente
1

Asumiré que filterA, filterB y filterC realmente modifican la lista de productos. De lo contrario, si son solo verificaciones si, entonces se pueden ignorar filterA y filterB ya que todas las rutas conducen finalmente a filterC. Su descripción del requisito parece implicar que cada filtro reducirá la lista de productos.

Asumiendo que los filtros realmente reducen la lista de productos, aquí hay un poco de pseudocódigo ...

class filter
    func check(item) returns boolean
endclass

func applyFilter(filter, productList) returns list
    newList is list
    foreach item in productList
        if filter.check(item) then
            add item to newList
        endif
    endfor 
    return newList
endfunc



filterA, filterB, filterC = subclasses of filter for each condition, chosen by the user
products = list of items to be filtered

if filterA then
    products = applyFilter(filterA, products)
endif

if filterB then
    products = applyFilter(filterB, products)
endif

if filterC then
    products = applyFilter(filterC, products)
endif

# use products...

En sus requisitos, filterC no se aplica automáticamente, pero en el diagrama sí. Si el requisito es que al menos filterC se debe aplicar sin importar qué, entonces llamaría a applyFilter (filterC, productos) sin verificar si se elige filterC.

filterC = instance of filter, always chosen

...

# if filterC then
products = applyFilter(filterC, products)
# endif
Kent A.
fuente
0

Me pregunto si modelar sus filtros para que sea algún tipo de objeto en un gráfico tendría sentido. Al menos eso es lo que pienso cuando veo el diagrama.

Si modela la dependencia de los filtros como un gráfico de objeto, entonces el código que maneja las posibles rutas de flujo es bastante sencillo sin ninguna lógica complicada. Además, el gráfico (lógica de negocios) puede cambiar, mientras que el código que interpreta el gráfico permanece igual.

enumeración
fuente