Se altrimenti - Logica di codice ripetuta


15

Il mio capo mi ha dato un progetto con una logica particolare. Devo sviluppare una pagina Web che deve guidare il navigatore in molti casi fino a quando non arriva al prodotto.

Questo è lo schema dei percorsi della navigazione nel sito:

Schema del percorso

IMPORTANTE!

Nella pagina Prodotti il ​​navigatore può scegliere quale filtro desidera.

  • Se A, DEVE passare attraverso la B (e poi la C ovviamente) o la C e raggiungere i prodotti.
  • Se B, DEVE passare attraverso la C e raggiungere i prodotti.
  • Se C, raggiunge direttamente i prodotti.

Ovviamente se parto dall'intelligenza artificiale sto seguendo il percorso più lungo e quando raggiungo i miei prodotti ho 3 filtri attivi.

Fino ad ora ho sviluppato il seguente codice che funziona bene.

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

Sono qui per chiedere cosa avrebbe fatto un programmatore più esperto in questa situazione. Non ho rispettato il principio DRY, non mi piace e mi piacerebbe conoscere un modo alternativo per sviluppare questo tipo di logica.

Ho pensato di dividere ogni sezione di codice in funzioni, ma è una buona idea in questo caso?



Il diagramma del flusso di controllo mostra tutto il controllo che passa filter_C, ma le istruzioni condizionali indicano che il flusso di controllo può andare in giro filter_C. È filter_Cfacoltativo?
CurtisHx

@CurtisHx Filter C è obbligatorio. Sì, scusate il mio errore, ho fatto copia-incolla.
Kevin Cittadini,

2
Come può questa domanda essere indipendente dalla lingua ? Una soluzione idiomatica in Java sarebbe molto diversa da una soluzione idiomatica in Haskell. Non hai deciso una lingua per il tuo progetto?
200_successo

Risposte:


20

Non hai detto se i filtri accettano parametri. Ad esempio, filter_Apotrebbe essere un filtro di categoria, quindi non è solo una questione di "devo applicare filter_A", potrebbe essere "Devo applicare filter_Ae restituire tutti i record con il campo categoria = fooCategory".

Il modo più semplice per implementare esattamente ciò che hai descritto (ma assicurati di leggere la seconda metà della risposta di seguito) è simile alle altre risposte, ma non avrei alcun controllo booleano. Vorrei definire le interfacce: FilterA, FilterB, FilterC. Quindi puoi avere qualcosa del tipo (sono un programmatore Java, quindi questa sarà sintassi Java-esque):

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

Quindi potresti avere qualcosa del genere (usando il modello enum singleton di Effective Java ):

enum NoOpFilterA implements FilterA {
    INSTANCE;

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

Ma se vuoi effettivamente filtrare alcuni elementi, puoi invece fornire un'istanza di FilterAun'implementazione che effettivamente fa qualcosa. Il tuo metodo di filtrazione sarà molto semplice

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;
}

Ma ho appena iniziato.

Sospetto che la applyFilterchiamata sarà effettivamente abbastanza simile per tutti e tre i tipi di filtri. In tal caso, non lo farei nemmeno nel modo sopra descritto. Puoi ottenere un codice ancora più pulito avendo solo un'interfaccia, quindi facendo questo:

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;
     }
}

Quindi, man mano che l'utente naviga tra le pagine, aggiungi semplicemente una nuova istanza di qualunque filtro ti serva quando appropriato. Ciò ti consentirà di applicare più istanze dello stesso filtro con argomenti diversi nel caso in cui avessi bisogno di quel comportamento in futuro, e anche di aggiungere filtri aggiuntivi in ​​futuro senza dover cambiare il design .

Inoltre, puoi aggiungere qualcosa di simile a quanto NoOpFiltersopra o semplicemente non puoi aggiungere un particolare filtro all'elenco, qualunque cosa sia più facile per il tuo codice.


Grazie, perché trovi il modo più semplice possibile per cambiare la logica senza cambiare anche il codice. Questo rende la tua risposta la migliore. Implementerò questo design di codice il prima possibile
Kevin Cittadini il

Se avessi il tuo Filtercome un Predicateallora potresti usarlo direttamente Streamnell'API. Molte lingue hanno costrutti funzionali simili.
Boris the Spider,

3
@BoristheSpider Questo è solo se sta usando Java 8; non ha nemmeno detto quale lingua stesse usando. Altre lingue hanno un tale costrutto ma non volevo approfondire tutti i diversi gusti su come farlo
durron597

3
Capito: vale la pena ricordare che questa è una strada da esplorare se l'OP vuole fornire l'implementazione più pulita possibile. Hai sicuramente il mio +1 per una risposta già eccellente.
Boris the Spider

3

In questo caso, è importante separare la logica del filtraggio e il flusso di controllo di come funzionano i filtri. La logica del filtro deve essere suddivisa in singole funzioni, che possono essere indipendenti l'una dall'altra.

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

Nel codice di esempio pubblicato, c'è 3 booleani filter_A, filter_Be filter_C. Tuttavia, dal diagramma, filter_Cviene sempre eseguito, quindi può essere modificato in un incondizionato.

NOTA: presumo che il diagramma di flusso di controllo sia corretto. Esiste una discrepanza tra il codice di esempio pubblicato e il diagramma di flusso di controllo.

Un pezzo separato di codice controlla quali filtri vengono eseguiti

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

Esiste una netta separazione tra il controllo di quali filtri vengono eseguiti e ciò che fanno i filtri. Spezza quei due pezzi di logica a parte.


+1 Questo sembra molto più semplice e disaccoppiato rispetto alla risposta accettata.
winkbrace,

2

Presumo che tu voglia l'algoritmo più semplice e chiaro.
In questo caso, sapendo che il filtro c è sempre applicato, lo vivrei fuori dalla logica if e lo applicherei alla fine indipendentemente. Come appare nel diagramma di flusso, ogni filtro prima della c è facoltativo, poiché ognuno di essi può essere applicato o meno. In questo caso, vivrei se separato da ciascun filtro, senza nidificazione e concatenamento:

if filter_a
  do_filter_a()

if filter_b
  do_filter_b()

do_filter_c()

se si dispone di un diagramma di flusso con un numero variabile di filtri, prima di quello obbligatorio, invece, vorrei salvare tutti i filtri in un array, nell'ordine in cui dovrebbero apparire. Quindi elabora i filtri opzionali nel loop e applica quello obbligatorio alla fine, al di fuori del loop:

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)

di cortesia, dovresti definire la subroutine di elaborazione del filtro.


1

Presumo che filterA, filterB e filterC modifichino effettivamente l'elenco dei prodotti. Altrimenti, se sono solo if-check, allora filterA e filterB possono essere ignorati poiché tutti i percorsi portano infine a filterC. La descrizione del requisito sembra implicare che ciascun filtro ridurrà l'elenco dei prodotti.

Quindi supponendo che i filtri riducano effettivamente l'elenco dei prodotti, ecco un po 'di pseudo-codice ...

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...

Nei tuoi requisiti, filterC non viene applicato automaticamente, ma nel diagramma lo è. Se il requisito è che almeno filterC debba essere applicato, qualunque cosa accada, allora si chiamerà applyFilter (filterC, prodotti) senza verificare se è stato scelto filterC.

filterC = instance of filter, always chosen

...

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

0

Mi chiedo se modellare i tuoi filtri come una sorta di oggetti in un grafico avrebbe senso. Almeno questo è quello che penso quando vedo il diagramma.

Se si modella la dipendenza dei filtri come un grafico a oggetti, il codice che gestisce i possibili percorsi di flusso è praticamente semplice senza alcuna logica pelosa. Inoltre, il grafico (logica aziendale) può cambiare, mentre il codice che interpreta il grafico rimane lo stesso.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.