Лучший подход к программированию очень сложных бизнес / математических правил

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

Например:

if (isSoccer)
    val = soccerBaseVal;
else if (isFootball)
    val = footballBaseVal;
.... // 20 different sports

if (isMale)
   val += 1;
else
    val += 5;

switch(dayOfWeek)
{
    case DayOfWeek.Monday:
       val += 12;
    ...
}

и т. д. т. д. и т. д., возможно, в диапазоне от 100 до 200 различных тестов и вариаций формул.

Это просто похоже на кошмар обслуживания. Какие-либо предложения?

РЕДАКТИРОВАТЬ:

Проблема усугубляется тем, что многие переменные используются только в определенных ситуациях, поэтому это больше, чем просто фиксированный набор логики с разными значениями. Сама логика должна изменяться в зависимости от условий, возможно, условий, примененных из предыдущих переменных (например, если val> threshold).

Так что да, я согласен с использованием поиска для многих значений, но мне также нужна логика переменных.


person Erik Funkenbusch    schedule 29.06.2010    source источник
comment
Возможный обман: stackoverflow.com/ questions / 1607252 /   -  person Matt Stephenson    schedule 29.06.2010


Ответы (8)


Распространенный способ избежать больших коммутационных структур - поместить информацию в структуры данных. Создайте перечисление SportType и Dictionary<SportType, Int32>, содержащее связанные значения. Вы можете просто написать val += sportTypeScoreMap[sportType], и все готово.

Вариации этого рисунка помогут вам во многих подобных ситуациях.

public enum SportType
{
    Soccer, Football, ...
}

public sealed class Foo
{
    private static readonly IDictionary<SportType, Int32> sportTypeScoreMap =
        new Dictionary<SportType, Int32>
        {
            { Soccer, 30 },
            { Football, 20 },
            ...
        }

    private static readonly IDictionary<DayOfWeek, Int32> dayOfWeekScoreMap =
        new Dictionary<DayOfWeek, Int32>
        {
            { DayOfWeek.Monday, 12 },
            { DayOfWeek.Tuesday, 20 },
            ...
        }

    public Int32 GetScore(SportType sportType, DayOfWeek dayOfWeek)
    {
        return Foo.sportTypeScoreMap[sportType]
             + Foo.dayOfWeekScoreMap[dayOfWeek];
    }
}
person Daniel Brückner    schedule 29.06.2010
comment
Хороший. Я, наверное, воспользуюсь чем-то вроде этого. Однако я немного расширил проблему. Смотрите мою правку. - person Erik Funkenbusch; 29.06.2010
comment
Вы всегда будете достигать точки, когда становится невозможным (или полезным) извлекать больше функциональных возможностей в общих методах - это в идеале, когда все операции, которые вы должны выполнить, различны и не остается общего шаблона. Этот несжимаемый остаток (аналогия со сжатием данных вполне подходит - вы пытаетесь извлечь все общие закономерности, а все, что остается, похоже на бесструктурный случайный шум) все же может быть очень сложным. Лучшее, что можно сделать с остальным, - это как можно лучше разделить его на несколько небольших методов с хорошими именами. - person Daniel Brückner; 30.06.2010
comment
Это в точности вариант использования мощного enum-класса Java - вы можете связать дополнительные данные с самим enum-value, исключая необходимость во всех этих словарях (и позволяя вам, например, поместите GetScore () в класс перечисления, чтобы вы могли вызвать sportType.GetScore(dayOfWeek)). Одна из немногих вещей, которые Java сделала правильно, а C # - нет ... - person BlueRaja - Danny Pflughoeft; 30.06.2010
comment
См. Также: здесь. Он настолько сложен, что действительно полезен только в том случае, если вам нужно много подобных словарей ... - person BlueRaja - Danny Pflughoeft; 30.06.2010

Используйте оператор switch или функцию фильтрации.

Под функцией фильтра я имею в виду что-то вроде:

func filter(var object, var value)
{
    if(object == value)
        object = valueDictionary['value'];
}

Затем примените фильтр с:

filter(theObject, soccer)
filter(theObject, football)

Обратите внимание, что фильтр работает намного лучше со словарем, но это не обязательно.

person samoz    schedule 29.06.2010

Взяв за основу «Прагматичный программист», вы можете использовать DSL для инкапсуляции правил и написания механизма процесса. Для представленной проблемы решение может выглядеть так:

MATCH{
    Soccer   soccerBaseVal

    IsMale   5
    !IsMale  1
}

SWITCH{
    Monday   12
    Tuesday  13
}

Затем сопоставьте все в первом столбце MATCH и первом элементе каждого SWITCH, к которому вы придете. Вы можете создать любой синтаксис, который вам нравится, а затем просто написать немного скрипта, чтобы втиснуть его в код (или использовать Xtext, потому что он выглядит довольно круто).

person Adam Shiemke    schedule 29.06.2010

Вот несколько идей:

1 Используйте таблицы поиска:

var val = 0;

SportType sportType = GetSportType();

val += sportvalues[sportType];

Вы можете загрузить таблицу из базы данных.

2 Используйте заводской узор:

var val = 0;

val += SportFactory.Create(sportType).CalculateValue();

Dynamic Factory Pattern полезен в новых ситуациях (спорт ) типы часто добавляются в код. Этот шаблон использует отражение, чтобы предотвратить изменение фабричного класса (или любой глобальной конфигурации). Это позволяет вам просто добавить новый класс в ваш код.

Конечно, использование динамической фабрики или даже фабрики может быть излишним в вашей ситуации. Ты единственный, кто может сказать.

person Steven    schedule 29.06.2010

В качестве первого шага я бы, вероятно, разбил каждую область логической обработки на отдельный метод: (Может быть, это не лучшие имена в качестве первого прохода)

EnforceSportRules
ProcessSportDetails
EnforceGenderRules 

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

GenderRules
GenderContext
person blu    schedule 29.06.2010

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

Другое предложение: если у вас будет много очень коротких тестов, как в вашем примере, отойдите от соглашения и поместите инкременторы val в одну строку с оценкой и отступом, чтобы они совпадали друг с другом.

if (isSoccer)              val = soccerBaseVal;  
if (isMale)                val += 1; 
else                       val += 5; 

switch(dayOfWeek){ 
    case DayOfWeek.Monday: val += 12; 
    ... 
}  

Избыточный пробел может превратить эту сотню элементов в несколько сотен строк, из-за чего вертикальная прокрутка будет чрезмерной и сложной для получения общего представления.

person smdrager    schedule 29.06.2010

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

enum Sport
{
    football = 0,
    soccer   = 1,
    //...
}

int sportValues[] = { 
    /* footballValue */,
    /* soccerValue */,
    /* ...Values */
};

int ApplyRules(Sport sport, /* other params */)
{
    int value = startingValue;
    value += sportValues[(int)sport];
    value += /* other rules in same fashion */;
}
person Nick Larsen    schedule 29.06.2010

Рассмотрите возможность реализации шаблона стратегии, который использует наследование / полиморфизм, чтобы сделать управление отдельными функциями разумным. Разделив каждую функцию на отдельный выделенный класс, вы можете избавиться от кошмара, связанного с наличием длинных case блоков или if операторов.

Не уверен, поддерживает ли C # это еще (или когда-либо будет), но VB.NET интегрирует XML-комментарий CompletionList в intellisense, которые в сочетании с шаблоном стратегии могут дать вам простоту использования Enum с неограниченной расширяемостью OO.

person STW    schedule 29.06.2010