If Else - Repeated Code Logic

15

Mój szef dał mi projekt ze szczególną logiką. Muszę opracować stronę internetową, która musi prowadzić nawigatora przez wiele przypadków, aż dotrze on do produktu.

Oto schemat ścieżki nawigacji na stronie:

Schemat ścieżki

WAŻNY!

Na stronie Produkty nawigator może wybrać filtr, który chce.

  • Jeśli A, MUSI przejść przez B (a następnie oczywiście C) lub C i dotrzeć do produktów.
  • Jeśli B, MUSI przejść przez C i dotrzeć do produktów.
  • Jeśli C, on / ona dociera bezpośrednio do produktów.

Oczywiście, jeśli zacznę od sztucznej inteligencji, podążę najdłuższą ścieżką, a kiedy dotrę do moich produktów, mam 3 aktywne filtry.

Do tej pory opracowałem następujący kod, który działa dobrze.

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

Jestem tutaj, aby zapytać, co zrobiłby bardziej doświadczony programista w tej sytuacji. Nie przestrzegałem zasady DRY, nie podoba mi się to i chciałbym poznać alternatywny sposób na rozwinięcie tego rodzaju logiki.

Myślałem o podzieleniu każdej sekcji kodu na funkcje, ale czy w tym przypadku to dobry pomysł?

Kevin Cittadini
źródło
Diagram przepływu sterowania pokazuje całą kontrolę przechodzącą filter_C, ale instrukcje warunkowe wskazują, że przepływ sterowania może się zmieniać filter_C. Jest filter_Copcjonalny?
CurtisHx,
@CurtisHx Filtr C jest obowiązkowy. Tak, przepraszam, mój błąd, zrobiłem kopiuj-wklej.
Kevin Cittadini,
2
Jak to pytanie może być niezależne od języka ? Rozwiązanie idiomatyczne w Javie byłoby bardzo różne od rozwiązania idiomatycznego w Haskell. Nie zdecydowałeś się na język swojego projektu?
200_success

Odpowiedzi:

20

Nie powiedziałeś, czy filtry przyjmują jakieś parametry. Na przykład filter_Amoże to być filtr kategorii, aby nie było to tylko pytanie „czy muszę zastosować filter_A”, może to być „muszę zastosować filter_Ai zwrócić wszystkie rekordy z polem kategorii = fooCategory”.

Najprostszy sposób na wdrożenie dokładnie tego, co opisałeś (ale koniecznie przeczytaj drugą połowę odpowiedzi poniżej) jest podobny do innych odpowiedzi, ale w ogóle nie miałbym żadnych czeków logicznych. Chciałbym określić interfejsy: FilterA, FilterB, FilterC. Wtedy możesz mieć coś takiego (jestem programistą Java, więc będzie to składnia w stylu Java):

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

Wtedy możesz mieć coś takiego (używając wzorca singleton enum z Effective Java ):

enum NoOpFilterA implements FilterA {
    INSTANCE;

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

Ale jeśli faktycznie chcesz filtrować niektóre elementy, możesz zamiast tego podać instancję FilterAimplementacji, która faktycznie coś robi. Twoja metoda filtracji będzie bardzo prosta

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

Ale dopiero zaczynam.

Podejrzewam, że applyFilterpołączenie będzie w rzeczywistości bardzo podobne dla wszystkich trzech rodzajów filtrów. W takim przypadku nawet nie zrobiłbym tego w sposób opisany powyżej. Możesz uzyskać jeszcze czystszy kod, mając tylko jeden interfejs, a następnie robiąc to:

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

Następnie, gdy użytkownik porusza się po stronach, po prostu dodajesz nową instancję dowolnego filtra, którego potrzebujesz, w razie potrzeby. Umożliwi to stosowanie wielu wystąpień tego samego filtra z różnymi argumentami, jeśli będziesz potrzebować takiego zachowania w przyszłości, a także dodawanie dodatkowych filtrów w przyszłości bez konieczności zmiany projektu .

Dodatkowo możesz dodać coś takiego jak NoOpFilterwyżej lub po prostu nie możesz w ogóle dodać konkretnego filtru do listy, cokolwiek jest łatwiejsze dla twojego kodu.

durron597
źródło
Dziękujemy, ponieważ znajdujesz najprostszy możliwy sposób zmiany logiki bez zmiany kodu. To sprawia, że ​​twoja odpowiedź jest najlepsza. Zaimplementuję ten projekt kodu JAK NAJSZYBCIEJ
Kevin Cittadini,
Jeśli miałeś swój Filterjako, Predicatemożesz użyć go bezpośrednio w Streaminterfejsie API. Wiele języków ma podobne konstrukcje funkcjonalne.
Boris the Spider
3
@ BoristheSpider To tylko wtedy, gdy używa Java 8; nawet nie powiedział, jakiego języka używa. Inne języki mają taką konstrukcję, ale nie chciałem wchodzić w różne smaki, jak to zrobić
durron597
3
Zrozumiane - warto wspomnieć, że jest to droga do zbadania, czy PO chce zapewnić możliwie najczystszą możliwą implementację. Z pewnością masz moją +1 za już doskonałą odpowiedź.
Boris the Spider
3

W takim przypadku ważne jest, aby oddzielić logikę filtrowania i przepływ kontrolny działania filtrów. Logika filtrowania powinna być podzielona na poszczególne funkcje, które mogą działać niezależnie od siebie.

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

W przykładowym kodzie pisał, tam 3 wartości logicznych filter_A, filter_Boraz filter_C. Jednak z diagramu filter_Czawsze działa, więc można go zmienić na bezwarunkowy.

UWAGA: Zakładam, że schemat kontroli jest prawidłowy. Występuje rozbieżność między wysłanym kodem próbki a diagramem kontroli.

Osobny fragment kodu kontroluje uruchamianie filtrów

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

Istnieje wyraźny rozdział między kontrolowaniem, które filtry działają, a tym, co robią filtry. Rozbij te dwa elementy logiki.

CurtisHx
źródło
+1 To wydaje się o wiele prostsze i oddzielone od przyjętej odpowiedzi.
winkbrace,
2

Zakładam, że chcesz najprostszego, najczystszego algorytmu.
W tym przypadku, wiedząc, że filtr c jest zawsze stosowany, chciałbym przeżyć go poza logiką if i zastosować go na końcu, niezależnie od tego. Jak widać na schemacie blokowym, każdy filtr przed literą c jest opcjonalny, ponieważ każdy z nich można zastosować lub nie. W takim przypadku żyłbym, gdyby oddzielne od każdego filtra, bez zagnieżdżania i łączenia:

if filter_a
  do_filter_a()

if filter_b
  do_filter_b()

do_filter_c()

jeśli masz schemat blokowy ze zmienną liczbą filtrów, przed obowiązkowym, zapisałbym wszystkie filtry w tablicy, w kolejności, w jakiej powinny się pojawić. Następnie przetworz opcjonalne filtry w pętli i zastosuj obowiązkowy na końcu, poza pętlą:

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()

lub:

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)

oczywiście, musisz zdefiniować podprogram przetwarzania filtrów.

igoryonya
źródło
1

Zakładam, że filterA, filterB i filterC faktycznie modyfikują listę produktów. W przeciwnym razie, jeśli są one tylko sprawdzeniami typu if, wtedy filterA i filterB można zignorować, ponieważ wszystkie ścieżki prowadzą ostatecznie do filterC. Twój opis wymagania wydaje się sugerować, że każdy filtr zmniejszy listę produktów.

Więc zakładając, że filtry faktycznie zmniejszają listę produktów, oto trochę pseudo-kodu ...

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

W twoich wymaganiach filterC nie jest stosowane automatycznie, ale na schemacie tak jest. Jeśli wymaganie jest takie, że przynajmniej filtr C powinien być zastosowany bez względu na wszystko, wtedy wywołałbyś ApplyFilter (filterC, produkty) bez sprawdzania, czy wybrano filterC.

filterC = instance of filter, always chosen

...

# if filterC then
products = applyFilter(filterC, products)
# endif
Kent A.
źródło
0

Zastanawiam się, czy modelowanie filtrów, aby były jakimiś obiektami na wykresie, miałoby sens. Przynajmniej o tym myślę, widząc schemat.

Jeśli modelujesz zależność filtrów jak wykres obiektowy, to kod, który obsługuje możliwe ścieżki przepływu, jest dość prosty, bez jakiejkolwiek kosmicznej logiki. Również wykres (logika biznesowa) może się zmieniać, a kod interpretujący wykres pozostaje taki sam.

wyliczanie
źródło