Существует ли более элегантный способ написания логики для нескольких проверяемых флажков, кроме нескольких операторов if? - PullRequest
0 голосов
/ 08 июля 2019

Я пишу приложение для форм Windows, в котором есть 4 флажка, которые можно установить одновременно.Я хочу проверить, какие флажки отмечены, и выполнять определенную логику, основываясь на том, какие флажки установлены.Проблема в том, что если я в конечном итоге попытаюсь написать код, чтобы проверить каждую возможную комбинацию флажков, мне придется написать длинный список операторов if else, и это без проверки, если есть только один проверенный.Есть ли более элегантный способ проверить, установлены ли несколько флажков?или я застрял, используя логику if-else?

//This is the type of code I am trying to avoid
//check all the conditions
//check1 is a check to add keys
//check2 is to create docs
//check3 is to create keys
//check4 is to reorder keys
//createKeys() creates keywords
//createDocs() creates documents
//addKeys() adds keywords
//reorderKeys reorders the keywords
if(check1 && check2 && check3 && check4){
    createKeys();
    createDocs();
    addKeys();
    reorderKeys();
}
else if(check1 && check2 && check3){
    createKeys();
    createDocs();
    addKeys();
}
else if(check1 && check2 && check4){
    createDocs();
    addKeys();
    reorderKeys();
}
else if(check1 && check2){
    createDocs();
    addKeys();
}
else if(check1){
    addKeys();
}
else if(check2 && check3 && check4){
    createKeys();
    createDocs();
    reorderKeys();
}
else if(check2 && check4){
    createDocs();
    reorderKeys();
}
else if(check2){
    createDocs();
}
else if(check3 && check4){
    createKeys();
    reorderKeys();
}
else if(check3){
    createKeys();
}
else if(check4){
    reorderKeys();
}

Обновление кода будет более конкретным

Ответы [ 5 ]

6 голосов
/ 08 июля 2019

Альтернативой if/else будет таблица методов.Вы можете создать словарь и поместить весь код как Action

var methodTable = new Dictionary<(bool, bool, bool, bool), Action>
{
    [(false, false, false, true)] = () => { /* do something */ },
    [(false, false, true, false)] = () => { /* do something else */ },
    [(false, false, true, true)] = SomeClassMethod,
    // and so on
};

Теперь вы можете вызывать код из блока if/else одной строкой:

methodTable[(check1, check2, check3, check4)].Invoke();

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

Если есть какая-то комбинация флажков, которые вы не хотите обрабатывать, у вас есть варианты:

Добавить пустые обработчики в methodTable, например:

[(false, false, false, true)] = () => {}, // do nothing

Или (что лучше для меня) использовать TryGetValue метод:

if (methodTable.TryGetValue((check1, check2, check3, check4), out var method))
    method.Invoke();
2 голосов
/ 08 июля 2019

( Я знаю, что у вас уже есть ответ, рассмотрите эту опцию как возможную альтернативу в другом случае ).

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

Например, ваш метод createKeys() имеет более высокий приоритет при выборе, createDocs() на один уровень меньше, но его необходимо выполнить до того, как другие, когда они тоже активны и так далее.

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

Используя класс ActionManager, вы можете связать действия с элементами управления, добавить их в класс, затем вызвать PerformActions(), чтобы выполнить все активные методы:

ActionManager.Actions.AddRange(new[] {
    new ActionManager.ActionControl(() => CreateKeys(), this.chkCreateKeys, 1),
    new ActionManager.ActionControl(() => CreateDocs(), this.chkCreateDocs, 2),
    new ActionManager.ActionControl(() => AddKeys(), this.chkAddKeys, 3),
    new ActionManager.ActionControl(() => ReorderKeys(), this.chkReorderKeys, 4)
});

При необходимости просто позвоните:

 ActionManager.PerformActions(); 

и все методы выполняются.

Вот как это происходит при CheckedChanged событии (может быть нажатие кнопки или что-то еще):

CheckBox Manager class

ActionManager класс:

public class ActionManager
{
    public static List<ActionControl> Actions { get; set; } = new List<ActionControl>();
    public static void PerformActions() {
        var sequence = Actions.Where(c => c.Control.Checked).OrderBy(a => a.ActionPriority);
        foreach (var action in sequence) {
           action.Action?.Invoke();
        }
    }
    public class ActionControl
    {
        public ActionControl(Action action, CheckBox control, int priority) {
            this.Action = action;
            this.Control = control;
            this.ActionPriority = priority;
        }
        public Action Action { get; }
        public CheckBox Control { get; }
        internal int ActionPriority { get; }
    }
}
2 голосов
/ 08 июля 2019

Используйте перечисление:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication46
{
    public partial class Form1 : Form
    {
        enum CheckBoxEnum
        {
            CHECKBOXNONE = 0,
            CHECKBOX1 = 1,
            CHECKBOX2 = 2,
            CHECKBOX3 = 4,
            CHECKBOX4 = 8,
        }

        List<CheckBox> checkboxes;
        CheckBoxEnum boxEnum = CheckBoxEnum.CHECKBOXNONE;
        public Form1()
        {
            InitializeComponent();

            checkboxes = new List<CheckBox>() { checkBox1, checkBox2, checkBox3, checkBox4 };

            foreach (CheckBox box in checkboxes)
            {
                box.Click += new EventHandler(box_Click);
            }

            switch (boxEnum)
            {
                case CheckBoxEnum.CHECKBOX1 | CheckBoxEnum.CHECKBOX3 :
                    break;
            }





        }
        private void box_Click(object sender, EventArgs e)
        {
            CheckBox box = sender as CheckBox;
            if (box.Checked)
            {
                boxEnum |= (CheckBoxEnum)(1 << checkboxes.IndexOf(box));
            }
            else
            {
                boxEnum &= ~(CheckBoxEnum)(1 << checkboxes.IndexOf(box));
            }
        }
    }
}
1 голос
/ 08 июля 2019

Для 4-х флажков вы можете сделать что-то вроде этого:

Добавить их в массив

bool[] checkboxes = { check1, check2, check3, check4 };

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

var selected = checkboxes
                     .Reverse() // reversing to get result with correct order
                     .Select((s, i) => new { IsChecked = s, Index = Math.Pow(10, i) })
                     .Where(w => w.IsChecked)
                     .Sum(s => s.Index);

, поэтому вы получите 1100 для true, true, false, false или 101 для false, true, false, true и т. Д.

и вы можете иметь Dictionary<double, Action> для всех возможностей, например:

Dictionary<double, Action> methods = new Dictionary<double, Action>
{
    { 0000,  YourMethod },
    { 0001,  YourMethod1 },
    { 0010,  YourMethod2 },
    { 0011,  YourMethod3  },
    { 0100,  YourMethod4  },
    { 0101,  YourMethod5  },
    { 0110,  YourMethod6  },
    { 0111,  YourMethod7  },
    { 1000,  YourMethod8  },
    { 1001,  YourMethod9  },
    { 1010,  YourMethod10  },
    { 1011,  YourMethod11  },
    { 1100,  YourMethod12  },
    { 1101,  YourMethod13  },
    { 1110,  YourMethod14  },
    { 1111,  YourMethod15  },
};

и назовите это так:

 methods[selected]();
1 голос
/ 08 июля 2019

Мы будем использовать наборы чисел, представляющие, какие флажки установлены, поэтому {1, 3} означает check1 && check3.

Если вся логика полностью отделена, т.е. нет никакой связи между действиями, предпринятыми для {a_1, a_2, ..., a_n} и {a_1, a_2, ..., a_n, x} для любых a_1, ..., a_n и x, тогда, очевидно, вам необходимо иметь 2^n отдельных процедур для каждой комбинации.Это означает, что в любом случае мы должны перейти в 2^n различных потоков кода.Таким образом, вопрос сводится к тому, можем ли мы решить, какой набор использовать менее затратным для кода способом.

Очевидно, вам нужно каким-то образом связать поведение с различными наборами.Это означает, что независимо от того, что мы делаем, в предположении, что у нас может быть 2^n полностью несвязанных процедур, по одной для каждого набора, вы все равно должны иметь как минимум 2^n операторов, которые свяжут определенный набор с определенной процедурой.Таким образом, короткий ответ - нет, даже если вы избавились от операторов if/else, например, закодировав проверки как последовательности битов, а затем отобразив эти последовательности в процедуры, вы бы избавились от логики здесь, но где-то еще вкод, который вы должны иметь такие сопоставления, как:

Dictionary<string, Action> logic = new Dictionary<int[], Action>();

logic["{1}"] = DoStuffFor1;
logic["{2}"] = DoStuffFor2;
...
logic["{N}"] = DoStuffForN;
logic["{1, 2}"] = DoStuffFor12;
logic["{1, 3}"] = DoStuffFor13;
...

var checkboxSet = Encode(check1, check2, ..., checkN); // Implementation of Encode should be obvious.
logic[checkboxSet].Invoke();

так что все еще 2^n операторов.Таким образом, для общего случая нет способа уменьшить количество строк, которые вы пишете, но вы можете найти карту делегатов более элегантной, чем рой if/else операторов.

Если, однако, логика дляразличные наборы флажков каким-то образом связаны между собой, что будет иметь место в любом разумном приложении из реальной жизни, возможно, вы могли бы свернуть операторы if, чтобы сократить код.Но без более подробной информации о реальной логике программы невозможно сказать.

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

На самом деле, теперь, когда я об этом думаю, вы может иметь очень запутанный способ разрешения имени метода во время выполнения, используя отражение и используя соглашение об именах для ваших методов, чтобы имена всех обработчиков были однозначными (например, имя метода, обрабатывающего {1, 42, 100}, было бы названоDoStuffFor_1_42_100 и т. Д.) Вы можете получить метод с именем, эквивалентным установленным флажкам, и вызвать его.Тем не менее, вам все равно потребуется написать 2^n процедуры.Также я думаю, что мы можем единодушно согласиться с тем, что это ужасная идея - использовать рефлексию для этой вещи и полагаться на соглашение об именах для работы кода.

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