If Else - повторная кодовая логика

15

Мой босс дал мне проект с особой логикой. Я должен разработать веб-страницу, которая должна вести навигатор по многим случаям, пока он не достигнет продукта.

Вот схема пути навигации по сайту:

Схема пути

ВАЖНЫЙ!

На странице Продукты навигатор может выбрать, какой фильтр он хочет.

  • Если A, он / она ДОЛЖЕН пройти через B (и, конечно, C) или C и добраться до продуктов.
  • Если B, он / она ДОЛЖЕН пройти через C и добраться до продуктов.
  • Если C, он / она достигает непосредственно продуктов.

Конечно, если я начинаю с ИИ, я иду по самому длинному пути, и когда я достигаю своих продуктов, у меня есть 3 активных фильтра.

До сих пор я разработал следующий код, который отлично работает.

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

Я здесь, чтобы спросить, что бы сделал более опытный программист в этой ситуации. Я не уважал принцип СУХОЙ, мне это не нравится, и я хотел бы знать альтернативный способ разработки такой логики.

Я думал о разделении каждого раздела кода в функциях, но это хорошая идея в этом случае?

Кевин Читтадини
источник
Диаграмма потока управления показывает все элементы управления filter_C, но условные операторы указывают, что поток управления может проходить filter_C. Является filter_Cнеобязательным?
CurtisHx
@CurtisHx Фильтр C обязателен. Да, извините, моя ошибка, которую я сделал copy-paste.
Кевин Читтадини
2
Как этот вопрос может быть не зависящим от языка ? Идиоматическое решение в Java будет очень отличаться от идиоматического решения в Haskell. Вы не определились с языком для своего проекта?
200_success

Ответы:

20

Вы не сказали, принимают ли фильтры какие-либо параметры. Например, это filter_Aможет быть фильтр категорий, так что это не просто вопрос «нужно ли мне применять filter_A», это может быть «мне нужно применить filter_Aи вернуть все записи с полем категории = fooCategory».

Самый простой способ реализовать именно то, что вы описали (но обязательно прочитайте вторую половину ответа ниже) , похож на другие ответы, но у меня не будет никаких булевых проверок вообще. Я хотел бы определить интерфейсы: FilterA, FilterB, FilterC. Тогда у вас может быть что-то вроде (я программист на Java, так что это будет синтаксис Java-esque):

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

Тогда вы можете получить что-то вроде этого (используя шаблон enum singleton из Effective Java ):

enum NoOpFilterA implements FilterA {
    INSTANCE;

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

Но если вы действительно хотите, чтобы некоторые элементы были отфильтрованы, вы можете вместо этого предоставить экземпляр FilterAреализации, который действительно что-то делает. Ваш метод фильтрации будет очень простым

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

Но я только начинаю.

Я подозреваю, что applyFilterвызов на самом деле будет очень похожим для всех трех типов фильтров. Если это так, я бы даже не сделал это так, как описано выше. Вы можете получить более чистый код, имея только один интерфейс, а затем сделайте следующее:

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

Затем, когда ваш пользователь перемещается по страницам, вы просто добавляете новый экземпляр того фильтра, который вам необходим, когда это необходимо. Это позволит вам иметь возможность применять несколько экземпляров одного и того же фильтра с разными аргументами, если вам понадобится такое поведение в будущем, а также добавлять дополнительные фильтры в будущем, не меняя дизайн .

Кроме того, вы можете добавить что-то подобное NoOpFilterвышеописанному или просто не добавлять какой-либо конкретный фильтр в список, что проще для вашего кода.

durron597
источник
Спасибо, потому что вы нашли самый простой способ изменить логику без изменения кода. Это делает ваш ответ лучшим. Я буду реализовывать этот дизайн кода как можно скорее
Кевин Читтадини
Если у вас есть Filterкак, Predicateто вы можете использовать его непосредственно в StreamAPI. Многие языки имеют похожие функциональные конструкции.
Борис Паук
3
@BoristheSpider Это только если он использует Java 8; он даже не сказал, какой язык он использует. У других языков есть такая конструкция, но я не хотел вдаваться во все различные варианты того, как это сделать
durron597
3
Понятно - просто стоит упомянуть, что это путь для изучения, если ОП хочет обеспечить максимально чистую реализацию. У вас, конечно, есть мой +1 за уже отличный ответ.
Борис Паук
3

В этом случае важно разделить логику фильтрации и поток управления работой фильтров. Логика фильтра должна быть разделена на отдельные функции, которые могут работать независимо друг от друга.

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

В примере кода в курсе, что есть 3 булевы filter_A, filter_Bи filter_C. Тем не менее, из диаграммы filter_Cвсегда выполняется, так что может быть изменен на безусловный.

ПРИМЕЧАНИЕ. Я предполагаю, что диаграмма потока управления правильная. Существует несоответствие между размещенным образцом кода и блок-схемой управления.

Отдельная часть кода контролирует, какие фильтры запускаются

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

Существует четкое разделение между контролем того, какие фильтры работают, и что делают фильтры. Разбейте эти две части логики на части.

CurtisHx
источник
+1 Это кажется намного проще и не связано с принятым ответом.
winkbrace
2

Я предполагаю, что вы хотите самый простой, понятный алгоритм.
В этом случае, зная, что фильтр c всегда применяется, я бы использовал его в логике if и применил бы его в конце независимо. Как видно на вашей потоковой диаграмме, каждый фильтр до c необязателен, потому что каждый из них может быть применен или нет. В этом случае я бы жил, если бы отдельно от каждого фильтра, без вложенности и цепочки:

if filter_a
  do_filter_a()

if filter_b
  do_filter_b()

do_filter_c()

если у вас есть блок-схема с переменным числом фильтров, перед обязательным я бы вместо этого сохранил все фильтры в массив в порядке их появления. Затем обработайте необязательные фильтры в цикле и примените обязательный в конце вне цикла:

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

или:

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)

Конечно, вы должны определить подпрограмму обработки фильтра.

igoryonya
источник
1

Я собираюсь предположить, что filterA, filterB и filterC действительно изменяют список продуктов. В противном случае, если они просто проверки if, то filterA и filterB можно игнорировать, поскольку все пути в конечном итоге ведут к filterC. Ваше описание требования подразумевает, что каждый фильтр будет сокращать список товаров.

Если предположить, что фильтры на самом деле сокращают список продуктов, вот немного псевдокода ...

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

В ваших требованиях filterC не применяется автоматически, но на диаграмме это так. Если требуется, чтобы по крайней мере что-то применялось filterC, то вы бы вызвали applyFilter (filterC, products), не проверяя, выбран ли filterC.

filterC = instance of filter, always chosen

...

# if filterC then
products = applyFilter(filterC, products)
# endif
Кент А.
источник
0

Интересно, имеет ли смысл моделировать ваши фильтры в виде объектов на графике. По крайней мере, это то, о чем я думаю, когда вижу диаграмму.

Если вы моделируете зависимость фильтров как граф объектов, то код, который обрабатывает возможные пути потока, в значительной степени прост без какой-либо сложной логики. Кроме того, график (бизнес-логика) может измениться, в то время как код, который интерпретирует график, остается неизменным.

перечисление
источник