C # заменяет огромный оператор if-else - PullRequest
0 голосов
/ 19 сентября 2018

скажем, у меня есть объект Item, который в основном содержит свойства перечисления, подобные этому

public enum Shape
{
    square = 1,
    trangle = 2,
    any = 3
}

public enum Color
{
    blue = 1,
    red = 2,
    yellow = 3,
    green = 4
}


public enum Material
{
    glass = 1,
    wood = 2,
    metal = 3
}


public class Item
{
    public Shape ItemShape { get; set; }
    public Color ItemColor { get; set; }
    public Material ItemMaterial { get; set; }
}

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

Я думал использовать комбинацию if-else, такую ​​как:

if(item.ItemShape == Shape.square && item.ItemColor == Color.blue && item.ItemMaterial == Material.glass)
        {
            //call Action1
        }
        else if(item.ItemShape == Shape.square && item.ItemColor == Color.blue && item.ItemMaterial == Material.wood)
        {
            // action2
        }
        ......

Проблема в том, что у меня около 16 комбинаций, так что это будет огромный метод if-else, чтобы решить, какой метод я должен вызвать позже.

enter image description here

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

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

Ответы [ 4 ]

0 голосов
/ 19 сентября 2018
void RunAction((Shape shape, Color color, Material material) item)
{
    switch(item)
    {
        case var i1 when i1.color == Color.red && i1.shape == Shape.square:
        case var i2 when i2.color == Color.blue:
            // Do action...
            break;
        case var i1 when i1.shape == Shape.trangle && i1.material == Material.metal:
            // Do action...
            break;
        default:
            // Do action...
            break;
    }
}

В этом решении используется значение кортежей , но в основном используется C # 7 сопоставление с шаблоном в операторах переключения.

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

0 голосов
/ 19 сентября 2018

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

Отказ от ответственности Обратите также внимание, что большой оператор if является лишь проблемой, если он распространяется / дублируется по всей базе кода и часто изменяется - помещая вещи в словарь не делаетдействительно уменьшить сложность .Наличие методов в словаре также меняет семантику вашего кода.Первый вопрос должен быть - я я изменяю сопоставления во время выполнения?Должны ли они действительно быть динамическими?

  1. Dictionary с ключом ValueTuple struct, вы можете использовать синтаксис (Shape, Color, Material) - это самый простой. Обратите внимание, - не класс Tuple, а ValueTuple struct.
  2. Dictionary с самим классом ключа Item, но тогда вам потребуется позаботьтесь о правильном сравнении равенств в Item.Вы можете сделать Item a struct , чтобы получить это бесплатно (но с более низкой производительностью, которая получается из сравнений на равенство в System.ValueType, использующих отражение в общем случае), или оставить его как класс (илиstruct) и реализовать IEquatable<Item>, Equals и GetHashCode.
    Без правильного сравнения на равенство поиск по словарю не будет работать (как предложено @ckuri)
  3. ИспользованиеValueTuple без словаря, чтобы просто сжать ваш код.
  4. A вариант шаблона State , где у вас есть общий интерфейс / базовый класс для вашего обработчика.Обработчик - это отдельный класс, который содержит одно действие для определенного набора значений, например AnyBlueGlassHandler : Handler.Каждый обработчик проверяет условие If и, если он равен true, выполняет действия.Затем вы можете поместить обработчики в List<T> и применить их к элементу типа handlers.Foreach(x=>x.Handle(item))

Код, когда Item является ключом, может выглядеть следующим образом:

    public static class ItemExtensions
    {
    static Dictionary<Item, Action<Item>>
        methods = new Dictionary<Item, Action<Item>>()
        {
            { new Item(Shape.any, Color.blue, Material.glass), x=> { /*do something*/ } }
        };


    public static bool TryApply(this Item item)
    {
        if (methods.TryGetValue(item, out var action))
        {
            action(item);
            return true;
        }
        return false;
    }
}

код, когда ValueTuple является ключом, может выглядеть как

 public static class ItemExtensionsUsingValueTuple
 {
    static Dictionary<(Shape, Color, Material), Action<Item>>
        methods = new Dictionary<(Shape, Color, Material), Action<Item>>()
        {
            { (Shape.any, Color.blue, Material.glass), x=> { /*do something*/ } }
        };


    public static bool TryApply(this Item item)
    {
        if (methods.TryGetValue((item.ItemShape, item.ItemColor, item.ItemMaterial), out var action))
        {
            action(item);
            return true;
        }
        return false;
    }
}

, более сжатая версия вашего кода с ifs, может выглядеть следующим образом:

  • объявление свойства ключа вкласс вашего предмета
    public (Shape, Color, Material) Key => (ItemShape, ItemColor, ItemMaterial);
  • используйте более элегантный оператор if
    if ( item.Key == (Shape.any, Color.blue, Material.glass)) { }
0 голосов
/ 19 сентября 2018

Попробуйте Dictionary<T>, например:

static Dictionary<Tuple<Shape, Color, Material>, Action> s_Actions = new
  Dictionary<Tuple<Shape, Color, Material>, Action>() {
    {Tuple.Create(Shape.square, Color.blue, Material.glass), () => { ... } },
     ...  
    {Tuple.Create(Shape.any, Color.red, Material.metal), () => { ... } },
     ...
  };

private static void RunAction(MyItem item) {
  Action action;

  // Either exact match or match with Any 
  if (!s_Actions.TryGetValue(Tuple.Create(item.ItemShape, item.ItemColor, item.ItemMaterial), 
                             out action))
    action = s_Actions.FirstOrDefault(pair => pair.Key.Item1 == Color.any &&
                                              pair.Key.Item2 == item.ItemColor &&
                                              pair.Key.Item3 == item.ItemMaterial) 

  // Execute action if it's found
  if (action != null) 
    action();  
}
0 голосов
/ 19 сентября 2018

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

    public bool RunActionIf(Item item, Shape shape, Color color, Material material, Action action)
    {
        if(item.ItemShape == shape && item.ItemColor == color && item.ItemMaterial == material)
        {
            action();
            return true;
        }

        return false;
    }

    public void RunAction(Item item)
    {
        var result =
            RunActionIf(item, Shape.square, Color.blue, Material.glass, Action1) ||
            RunActionIf(item, Shape.square, Color.blue, Material.wood, Action2) ||
            /* Implement your whole table like this */;

        if(!result)
        {
            throw new ArgumentException("No matching rule found", nameof(item));
        }
    }

Основное преимущество этого метода состоит в том, что он корочеи с меньшими накладными расходами в декларации.Вы можете легко увидеть: форма X + цвет Y + материал Z = это действие.

Другое преимущество состоит в том, что легче реализовать определенные исключения, например, позволяя одному из параметров правила быть null, чтобы указатьлюбой цвет, или использовать Color.any для поддержки этого, хотя я думаю, что путать any в перечислении с другими цветами ... В любом случае, я отвлекся.Дело в том, что если вы хотите реализовать это, вы должны сделать это только в одном месте, не копируя его 16 раз.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...