Якщо інше - повторена логіка коду


15

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

Це схема шляху навігації на сайті:

Схема шляху

ВАЖЛИВО!

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

  • Якщо A, він / вона ПОВИНЕН пройти через B (а потім C звичайно) або C і дістатися до продуктів.
  • Якщо B, він / вона ОБОВ'ЯЗКОВО пройде через C та дістається до продуктів.
  • Якщо C, він / вона досягає безпосередньо продуктів.

Звичайно, якщо я починаю з AI, я слідую за найдовшим шляхом, і коли я дістаюсь до своїх продуктів, у мене є 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 ..

Я тут, щоб запитати, що зробив би більш досвідчений програміст у цій ситуації. Я не дотримувався принципу DRY, мені це не подобається, і я хотів би знати альтернативний спосіб розвитку такої логіки.

Я думав про розділення кожного розділу коду за функціями, але чи це в цьому випадку гарна ідея?



На схемі потоку управління показано весь контроль, який проходить filter_C, але умовні висловлювання вказують на те, що потік управління може обійтись filter_C. Не filter_Cобов’язково?
CurtisHx

@CurtisHx Filter C є обов'язковим. Так вибачте за мою помилку, я зробив copy-paste.
Кевін Циттадіні

2
Як це питання може бути мовно-агностичним ? Ідіоматичне рішення на Java буде сильно відрізнятися від ідіоматичного рішення в Haskell. Ви не визначилися з мовою для свого проекту?
200_успіх

Відповіді:


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вищезазначеного, або ви просто не можете додати певний фільтр до списку, що б не було легше для вашого коду.


Дякую, що ви знаходите найпростіший спосіб змінити логіку без зміни коду. Це робить вашу відповідь найкращою. Я реалізую цей дизайн коду якомога швидше
Кевін Циттадіні

Якби у вас був ваш 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();
}

Існує чіткий поділ між контролем того, які фільтри працюють, і тим, що фільтри роблять. Розбийте ці дві частини логіки.


+1 Це здається набагато простішим і відокремленим, ніж прийнята відповідь.
winkbrace

2

Я припускаю, що вам потрібен найпростіший, зрозумілий алгоритм.
У цьому випадку, знаючи, що фільтр c застосовується завжди, я би жив ним із логіки if і застосовував би його в кінці незалежно. Як виглядає у вашій блок-схемі, кожен фільтр перед с є необов'язковим, оскільки кожен з них може бути застосований, або ні. У цьому випадку я живу, якщо окремо від кожного фільтра, не вкладаючи і не вкладаючи ланцюги:

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)

Зрозуміло, вам доведеться визначити підпрограму обробки фільтра.


1

Я припускаю, що filterA, filterB і filterC фактично змінюють список продуктів. В іншому випадку, якщо вони є лише if-check, тоді 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, продукти), не перевіряючи, чи вибрано filterC.

filterC = instance of filter, always chosen

...

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

0

Цікаво, чи було б доцільним моделювання ваших фільтрів як якихось об’єктів у графі. Принаймні, про це я думаю, бачачи діаграму.

Якщо ви моделюєте залежність фільтрів як об'єктний графік, то код, який обробляє можливі шляхи потоку, є майже прямим вперед без будь-якої волохатої логіки. Також графік (бізнес-логіка) може змінюватися, тоді як код, який інтерпретує графік, залишається однаковим.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.