У меня четыре значения bool:
bool bValue1;
bool bValue2;
bool bValue3;
bool bValue4;
Допустимые значения:
Scenario 1 | Scenario 2 | Scenario 3
bValue1: true | true | true
bValue2: true | true | false
bValue3: true | true | false
bValue4: true | false | false
Так, например, такой сценарий неприемлем:
bValue1: false
bValue2: true
bValue3: true
bValue4: true
На данный момент я придумал этот оператор if для обнаружения плохих сценариев:
if (((bValue4 && (!bValue3 || !bValue2 || !bValue1)) ||
((bValue3 && (!bValue2 || !bValue1)) ||
(bValue2 && !bValue1) ||
(!bValue1 && !bValue2 && !bValue3 && !bValue4))
{
// There is some error
}
Можно ли улучшить / упростить логику этого оператора?
if (!((bValue1 && bValue2 && bValue3) || (bValue1 && !bValue2 && !bValue3 && !bValue4)))Вы можете использовать symPy: docs.sympy.org/latest/modules/logic.html
каковы сценарии на самом деле? Часто все становится намного проще, если вы просто даете вещам собственные имена, например bool scenario1 = bValue1 && bValue2 && bValue3 && bValue4;.
@ user463035818 Технически они представляют недели. Только первая неделя должна быть истинной. На второй неделе все 4 значения должны быть верными. Третья, четвертая и пятая недели должны быть верными только первые 3 недели.
Другой вариант: компилировать с GCC. Если вам посчастливилось скомпилировать с помощью gcc, позвольте ему сделать упрощение. Другие компиляторы не имеют такой возможности: godbolt.org/z/OJ8z7N
Используя осмысленные имена, вы можете выделить каждое сложное условие в метод и вызвать этот метод в условии if. Это было бы намного удобнее для чтения и обслуживания. например Взгляните на пример, представленный по ссылке.
Лучшее, что я могу сделать вручную - это bValue1 && (bValue2 == bValue3) && (bValue4<=bValue2). Это 4 оператора.
используйте переключатель / case: вы можете преобразовать все bool в целые 0 и 1. затем сдвинуть каждый bool на один бит. затем используйте переключатель / чехол для 3 сценариев. иногда переключатель / корпус более читабельны, но ИМО, сдвигающая часть делает код немного сложнее для среднего программиста по обслуживанию.
Это больше похоже на задание для перечисления, которое явно перечисляет набор возможных значений (с семантически значимыми именами), возможно, с конструктором, который обеспечивает правильный регистр для данного набора логических значений. Несмотря на то, что у вас есть 4 логических значения, у вас есть только 2 бита информации (состояние 1, состояние 2, состояние 3, недопустимое состояние)
В качестве общего комментария я хотел бы убедиться, что «сценарий» - правильное слово. В тех случаях, когда я видел «сценарий», использованный в прошлом, это перечисление. Если вы находитесь в сценарии 1, вы являетесь нет в сценарии 2. Другие слова, такие как «случай» и «событие» или даже «флаг», являются немного более стандартными для того, чтобы отметить, что несколько из них могут быть истинными.
@CortAmmon Это действительно немного не по теме. С точки зрения программирования это можно считать «перечислением». В реальной жизни данные представляют собой различные «сценарии» того, как предметы учащегося расставлены на собрании.
Никто раньше этого не говорил: ваш код и так хорош. Он напрямую отображает бизнес-логику, представленную в виде таблицы, в код. Это очень легко понять и поддерживать. Незначительный момент: удалите ненужные фигурные скобки, чтобы сделать его немного чище.
Множество разных ответов, сводящихся к расплывчатому вопросу "Можно ли улучшить / упростить логику этого утверждения?" Любой код подобен треугольнику «два выбора» между читаемостью / ремонтопригодностью, скоростью выполнения и размером кода / временем компиляции. Это приводит к тому, что пользователи говорят, что вы можете исключить ABC + AB'C'D' = A(BC + B'C'D'), чтобы немного повысить скорость, но при этом ухудшить читаемость. В дальнейшем уточняйте, какие из них имеют приоритет.
Обратите внимание, что bvalue4 на самом деле не имеет значения: случаи №1 и №2 означают, что вы можете отказаться от bvalue4 без каких-либо промахов.
@ADS Ага. Я просто сохраняю его, чтобы отразить дословный график недельных сценариев.
FYI en.wikipedia.org/wiki/Karnaugh_map
Для меня это станет классическим вопросом SO. Это яркий пример любви разработчиков к смекалке и сложности. Без других ограничений большинство предпочтительных ответов ужасно оцениваются по моей шкале читаемости. Когда вы достаточно долго кодируете, вы в конце концов понимаете, что, по крайней мере, в 99% случаев удобочитаемость - это, безусловно, самая важная вещь, к которой нужно стремиться. Исчезли глубоко вложенные IF, вариативные шаблоны, карты Карно, побитовые операции и отсутствие самодокументирования.





Как предлагает mch, вы можете:
if (!((bValue1 && bValue2 && bValue3) ||
(bValue1 && !bValue2 && !bValue3 && !bValue4))
)
где первая строка охватывает два первых хороших случая, а вторая строка охватывает последний.
Live Demo, где я поигрался и передает твои дела.
Я бы стремился к удобочитаемости: у вас всего 3 сценария, разберитесь с ними с 3 отдельными if:
bool valid = false;
if (bValue1 && bValue2 && bValue3 && bValue4)
valid = true; //scenario 1
else if (bValue1 && bValue2 && bValue3 && !bValue4)
valid = true; //scenario 2
else if (bValue1 && !bValue2 && !bValue3 && !bValue4)
valid = true; //scenario 3
Легко читать и отлаживать, ИМХО. Кроме того, вы можете назначить переменную whichScenario, продолжая работу с if.
Имея всего 3 сценария, я бы не пошел с чем-то вроде «если первые 3 значения верны, я могу избежать проверки четвертого значения»: это затруднит чтение и сопровождение вашего кода.
Не изящное решение, конечно, может быть, но в данном случае все в порядке: легко и читабельно.
Если ваша логика усложняется, выбросьте этот код и подумайте об использовании чего-нибудь еще для хранения различных доступных сценариев (как предлагает Зладек).
Мне очень нравится первое предложение, данное в этот ответ: легко читается, не подвержено ошибкам, легко обслуживается
(Почти) не по теме:
Я не пишу много ответов здесь, в StackOverflow. Это действительно забавно, что принятый выше ответ, безусловно, является самым ценным ответом в моей истории (я никогда не набирал больше 5-10 голосов), хотя на самом деле это не то, что я обычно считаю «правильным» способом сделать это.
Но простота часто является «правильным способом сделать это», многие люди, кажется, думают так, и я должен думать об этом больше, чем я :)
@gsmaras Обычно я ненавижу последовательность if, else if, but if, whatAboutThisIf и т. д. Но в данном конкретном случае это, кажется, (а?) Путь. Я поддержал ответ Зладека, может быть, труднее читать, но очень легко поддерживать
Единственным недостатком является масштабируемость ИМХО, что, если количество допустимых условий изменится позже до 10 или около того
конечно @hessamhedieh, это нормально только для небольшого количества доступных сценариев. как я уже сказал, если что-то станет сложнее, лучше поищите что-нибудь другое
В этой ситуации я пойду с этим ответом, потому что у меня не будет больше заявленных сценариев. Спасибо.
Это можно еще больше упростить, поместив все условия в инициализатор для valid и разделив их с помощью ||, вместо того, чтобы изменять valid в отдельных блоках операторов. Я не могу привести пример в комментарии, но вы можете вертикально выровнять операторы || по левому краю, чтобы сделать это очень ясным; отдельные условия уже заключены в круглые скобки столько, сколько они должны быть (для if), поэтому вам не нужно добавлять какие-либо символы в выражения, кроме того, что уже есть.
@Leushenko, я думаю, что круглые скобки, && и || Условия довольно подвержены ошибкам (кто-то в другом ответе сказал, что в коде в OP была ошибка в скобках, возможно, она была исправлена). Конечно, может помочь правильное выравнивание. Но в чем преимущество? читабельнее? легче поддерживать? Я так не думаю. Просто мое мнение, конечно. Будьте уверены, я действительно ненавижу много "если" в коде.
@hessamhedieh, см. мой дальнейший ответ, чтобы узнать о другом подходе, который можно масштабировать и как-то легко, но не так просто (и подходит), как этот
@GianPaolo, спасибо за ответ (проголосовало за :)), если бы это был я, я бы пошел на второй подход, я также опубликовал ответ в более C-ish стиле. посмотри здесь, если тебе нравится
Я бы обернул его в if ($bValue1), так как это всегда должно быть правдой, технически допуская небольшое улучшение производительности (хотя здесь мы говорим о ничтожных количествах).
@Martijn: Не стоит недооценивать компиляторы. Если повезет, они также могут определить общее состояние, которое вызывает bValue2==bValue3, поэтому последующие 3 проверки необходимы только для тестирования bValue2.
@Martijn, также не переоценивайте бедных разработчиков: им рано или поздно придется разбираться в коде, и им придется спросить себя: «Почему первый bool никогда не тестируется? Потратьте несколько циклов процессора, не тратьте время разработчиков. и старания :)
FWIW: есть только 2 сценария: первые 2 являются одним и тем же сценарием и не зависят от bValue4
Я в целом согласен с этим ответом, но все же считаю, что должно быть только одно назначение (фактически, одно инициализация), и что valid должен быть объявлен как const. Конечно, это возможно с использованием условного оператора, а не операторов if. Это не только улучшает правильность констант, но и улучшает читаемость.
Мне это кажется очень нечитаемым, а главное отличие - маленькие восклицательные знаки.
Почему бы не использовать здесь логическое И вместо логического И? Он будет таким же с точки зрения удобочитаемости, но исключит ветвления, вызванные операциями логического И.
@shogged Я тоже подумал, что это хорошее место для использования Ключевое слово not вместо почти невидимого !.
Настоящий вопрос здесь: что произойдет, если другой разработчик (или даже автор) должен изменить этот код несколько месяцев спустя.
Я бы предложил моделировать это как битовые флаги:
const int SCENARIO_1 = 0x0F; // 0b1111 if using c++14
const int SCENARIO_2 = 0x0E; // 0b1110
const int SCENARIO_3 = 0x08; // 0b1000
bool bValue1 = true;
bool bValue2 = false;
bool bValue3 = false;
bool bValue4 = false;
// boolean -> int conversion is covered by standard and produces 0/1
int scenario = bValue1 << 3 | bValue2 << 2 | bValue3 << 1 | bValue4;
bool match = scenario == SCENARIO_1 || scenario == SCENARIO_2 || scenario == SCENARIO_3;
std::cout << (match ? "ok" : "error");
Если существует намного больше сценариев или больше флагов, табличный подход более читабелен и расширяем, чем использование флагов. Для поддержки нового сценария требуется просто еще одна строка в таблице.
int scenarios[3][4] = {
{true, true, true, true},
{true, true, true, false},
{true, false, false, false},
};
int main()
{
bool bValue1 = true;
bool bValue2 = false;
bool bValue3 = true;
bool bValue4 = true;
bool match = false;
// depending on compiler, prefer std::size()/_countof instead of magic value of 4
for (int i = 0; i < 4 && !match; ++i) {
auto current = scenarios[i];
match = bValue1 == current[0] &&
bValue2 == current[1] &&
bValue3 == current[2] &&
bValue4 == current[3];
}
std::cout << (match ? "ok" : "error");
}
Не самый удобный в обслуживании, но определенно упрощает условие if. Так что оставить несколько комментариев вокруг побитовых операций здесь, imo, будет абсолютной необходимостью.
ИМО, таблица - лучший подход, поскольку она лучше масштабируется с дополнительными сценариями и флагами.
Мне нравится ваше первое решение, оно легко читается и может быть изменено. Я бы сделал 2 улучшения: 1: присвоить значения сценарию X с явным указанием используемых логических значений, например. SCENARIO_2 = true << 3 | true << 2 | true << 1 | false; 2: избегайте переменных SCENARIO_X, а затем сохраните все доступные сценарии в <std::set<int>. Добавление сценария будет чем-то вроде mySet.insert( true << 3 | false << 2 | true << 1 | false;, возможно, излишним для всего 3 сценария, OP принял быстрое, грязное и простое решение, которое я предложил в своем ответе.
Если вы используете C++ 14 или выше, я бы предложил вместо этого использовать двоичные литералы для первого решения - 0b1111, 0b1110 и 0b1000 намного понятнее. Вы, вероятно, также можете немного упростить это, используя стандартную библиотеку (std::find?).
Я считаю, что здесь двоичные литералы являются требованием минимальный для очистки первого кода. В нынешнем виде это совершенно загадочно. Описательные идентификаторы могут помочь, но я даже в этом не уверен. Фактически, битовые операции для получения значения scenario кажутся мне излишне подверженными ошибкам.
Мы можем использовать Карта Карно и свести ваши сценарии к логическому уравнению. Я использовал решатель Интернет-карта Карно со схемой для 4 переменных.
Это дает:
Меняя A, B, C, D на bValue1, bValue2, bValue3, bValue4, это не что иное, как:
bValue1 && bValue2 && bValue3 || bValue1 && !bValue2 && !bValue3 && !bValue4
Итак, ваш оператор if выглядит следующим образом:
if (!(bValue1 && bValue2 && bValue3 || bValue1 && !bValue2 && !bValue3 && !bValue4))
{
// There is some error
}
true.true к логическому уравнению добавление соответствующих комментариев, указывающих на сценарии true, является хорошей практикой.Хотя этот код технически верен, он требует множества комментариев, чтобы его отредактировал другой разработчик через несколько месяцев.
@ZdeslavVojkovic: Я бы просто добавил комментарий к уравнению. //!(ABC + AB'C'D') (By K-Map logic). Для разработчика было бы хорошее время изучить K-Maps, если он еще не знает их.
Я согласен с этим, но проблема в том, что проблема в том, что IMO не четко отображает проблемную область, то есть как каждое условие сопоставляется с конкретным сценарием, что затрудняет изменение / расширение. Что происходит, когда есть условия E и F и 4 новых сценария? Сколько времени нужно, чтобы правильно обновить эту инструкцию if? Как проверка кода проверяет, все ли в порядке? Проблема не в технической стороне, а в «деловой» стороне.
Я думаю, вы можете исключить A: ABC + AB'C'D' = A(BC + B'C'D') (это можно даже разложить на A(B ^ C)'(C + D'), хотя я бы осторожно назвал это «упрощением»).
@ P.W Этот комментарий кажется таким же понятным, как и код, и поэтому немного бессмысленен. Лучший комментарий объяснил бы, как вы на самом деле пришли к этому уравнению, то есть что оператор должен запускаться для TTTT, TTTF и TFFF. В этот момент вы могли бы просто написать эти три условия в коде и вообще не нуждаться в объяснении.
@ P.W Или, лучше, // Simplified by Karnaugh map; must match TTTT, TTTF and TFFF, который дает имя полный для облегчения поиска в Google, показывает предполагаемую логику и не пропускает никаких шагов в вычислении (потому что он их не показывает).
На самом деле я узнал об этом в университете и не думал, что люди на самом деле это используют: O
Я бы добавил несколько лишних скобок. Многие разработчики не совсем усвоили приоритет логических операторов (&& vs ||) в той же степени, что и арифметические операторы, поэтому круглые скобки не позволят читателям усомниться.
Ранее я написал ответ, описывающий, как использовать карту Карно, который может быть полезной сноской к этому ответу. electronics.stackexchange.com/a/298508/145753
Я узнал о картах Карно в цифровой инженерии, когда мы говорили о логических вентилях, где минимизация вентилей действительно имеет значение. Я не вижу смысла торговать читабельностью ради «скорости», если это не редкий случай, когда код нужно максимально оптимизировать. Просто мои 2 цента.
Я часто использую карты Карно для упрощения условий с помощью ряда логических входов. Когда я это делаю, я обычно нахожу, что сокращенная формула, правильно сформулированная, является наиболее кратким и ясным описанием того, что я пытался проверить. То есть карты Карно имеют тенденцию находить шаблон, который вы искали.
Это, конечно, правильный способ решить проблему упрощения логики, Карно даже не нужен для этой простой задачи. Похоже, многие программисты не знают о булевой алгебре.
Я бы предложил аналогичный ответ. Но я не достаточно терпелив, чтобы делать это так подробно. OP попросил упрощения, и я прочитал это как меньшее количество кода с более коротким выражением условия. Думаю, этот вопрос возникает из-за проблемы цифрового дизайна. Я рассматриваю все остальные ответы как ненужные дополнения к простой в остальном проблеме.
Это тот подход, который может быть подходящим при программировании микроконтроллера или чего-то с чрезвычайно ограниченной памятью.
Разве компилятор не должен этого делать?
Вот упрощенная версия:
if (bValue1 && (bValue2 == bValue3) && (bValue2 || !bValue4)) {
// acceptable
} else {
// not acceptable
}
Обратите внимание, конечно, это решение более запутанное, чем исходное, его смысл может быть труднее понять.
Обновление: MSalters в комментариях нашел еще более простое выражение:
if (bValue1&&(bValue2==bValue3)&&(bValue2>=bValue4)) ...
Да, но трудно понять. Но спасибо за предложение.
Я сравнил способность компиляторов упрощать выражения с вашим упрощением в качестве справки: обозреватель компилятора. gcc не нашел вашу оптимальную версию, но ее решение все еще хорошее. Clang и MSVC, похоже, не выполняют никакого упрощения логических выражений.
@AndrewTruckle: обратите внимание, что если вам нужна более читаемая версия, скажите об этом. Вы сказали «упрощенный», но соглашаетесь с еще более подробной версией, чем исходная.
@Oliv: спасибо за этот тест! Это доказывает, что, хотя компиляторы становятся все лучше и лучше, им еще есть чему поучиться :)
simple - действительно расплывчатый термин. Многие люди понимают это в этом контексте как более простое для понимания разработчиком, а не компилятором для генерации кода, поэтому более подробное может быть проще.
@ZdeslavVojkovic: "упрощенная логика высказываний". Это совсем не расплывчато. Это означает более простое выражение с меньшим количеством терминов.
Это совсем нетрудно понять, хотя я бы лично поменял местами второй и третий члены, чтобы дать bValue1 && (bValue2||!bValue4) && (bValue2==bValue3), так что абсолютное ограничение на bValue2 стоит перед относительным.
Я считаю, что запутывающая деталь - это последнее условие. Я попытался упростить и пришел к этому решению, за исключением того, что я использовал обычный / третий (?) Оператор (?:) для последнего шага, который субъективно более читабелен. Добавление объяснения того, как все работает, вероятно, лучше всего.
@IsmaelMiguel: когда логическая формула оптимизируется по количеству терминов, исходное значение обычно теряется. Но вокруг него можно поставить комментарий, чтобы было понятно, что он делает. Даже для принятого ответа комментарий не повредит.
Просто примечание, чтобы сказать, что этот ответ представляет собой способ C++ написать то же выражение в комментарии @Maciej Piechotka к ответу P.W на этот вопрос.
Я думаю, что лучше иметь простой (но трудный для понимания) код с комментарием, объясняющий, что он делает, а не странный сложный код, который вы пытались сделать более читабельным без каких-либо комментариев.
C / C++ способ
bool scenario[3][4] = {{true, true, true, true},
{true, true, true, false},
{true, false, false, false}};
bool CheckScenario(bool bValue1, bool bValue2, bool bValue3, bool bValue4)
{
bool temp[] = {bValue1, bValue2, bValue3, bValue4};
for(int i = 0 ; i < sizeof(scenario) / sizeof(scenario[0]); i++)
{
if (memcmp(temp, scenario[i], sizeof(temp)) == 0)
return true;
}
return false;
}
Этот подход является масштабируемым, так как если количество допустимых условий растет, вы легко просто добавляете их в список сценариев.
Хотя я почти уверен, что это неправильно. Предполагается, что компилятор использует только одно двоичное представление для true. Компилятор, который использует «все, что ненулевое, истинно», приводит к сбою этого кода. Обратите внимание, что true должен быть конвертировать на 1, просто он не обязательно должен быть хранится как таковой.
@MSalters, tnx, я понимаю вашу точку зрения, и я знаю, что, вроде 2 is not equal to true but evaluates to true, мой код не заставляет int 1 = true и работает до тех пор, пока все true преобразуются в одно и то же значение int, поэтому вот мой вопрос: почему компилятор должен действовать случайным образом о преобразовании true в базовый int, не могли бы вы подробнее рассказать?
Выполнение memcmp для проверки логических условий - это нет в стиле C++, и я также очень сомневаюсь, что это уже установленный способ C.
@hessamhedieh: Проблема в вашей логике "преобразование true в базовый int". Так работают компиляторы нет,
Ваш код увеличивает сложность с O (1) до O (n). Ни на одном языке не выходить - оставьте в стороне C / C++.
Я обозначаю a, b, c, d для ясности и A, B, C, D для дополнений.
bValue1 = a (!A)
bValue2 = b (!B)
bValue3 = c (!C)
bValue4 = d (!D)
Уравнение
1 = abcd + abcD + aBCD
= a (bcd + bcD + BCD)
= a (bc + BCD)
= a (bcd + D (b ^C))
Используйте любые уравнения, которые вам подходят.
Я также хотел бы предложить другой подход.
Моя идея состоит в том, чтобы преобразовать bools в целое число, а затем сравнить, используя вариативные шаблоны:
unsigned bitmap_from_bools(bool b) {
return b;
}
template<typename... args>
unsigned bitmap_from_bools(bool b, args... pack) {
return (bitmap_from_bools(b) << sizeof...(pack)) | bitmap_from_bools(pack...);
}
int main() {
bool bValue1;
bool bValue2;
bool bValue3;
bool bValue4;
unsigned summary = bitmap_from_bools(bValue1, bValue2, bValue3, bValue4);
if (summary != 0b1111u && summary != 0b1110u && summary != 0b1000u) {
//bad scenario
}
}
Обратите внимание, как эта система может поддерживать до 32 логических значений в качестве входных данных. замена unsigned на unsigned long long (или uint64_t) увеличивает поддержку до 64 случаев.
Если вам не нравится if (summary != 0b1111u && summary != 0b1110u && summary != 0b1000u), вы также можете использовать еще один метод вариативного шаблона:
bool equals_any(unsigned target, unsigned compare) {
return target == compare;
}
template<typename... args>
bool equals_any(unsigned target, unsigned compare, args... compare_pack) {
return equals_any(target, compare) ? true : equals_any(target, compare_pack...);
}
int main() {
bool bValue1;
bool bValue2;
bool bValue3;
bool bValue4;
unsigned summary = bitmap_from_bools(bValue1, bValue2, bValue3, bValue4);
if (!equals_any(summary, 0b1111u, 0b1110u, 0b1000u)) {
//bad scenario
}
}
Мне нравится этот подход, за исключением названия основной функции: «from bool… to Какие?» - Почему не явно, bitmap_from_bools или bools_to_bitmap?
да @KonradRudolph, я не мог придумать лучшего имени, кроме, может быть, bools_to_unsigned. Растровое изображение - хорошее ключевое слово; отредактировал.
Думаю, тебе нужен summary!= 0b1111u &&.... a != b || a != c всегда верно, если b != c
Легко заметить, что первые два сценария похожи - они разделяют большинство условий. Если вы хотите выбрать, в каком сценарии вы находитесь в данный момент, вы можете написать это так (это модифицированное решение @ gian-paolo):
bool valid = false;
if (bValue1 && bValue2 && bValue3)
{
if (bValue4)
valid = true; //scenario 1
else if (!bValue4)
valid = true; //scenario 2
}
else if (bValue1 && !bValue2 && !bValue3 && !bValue4)
valid = true; //scenario 3
Двигаясь дальше, вы можете заметить, что первое логическое значение всегда должно быть истинным, что является условием входа, поэтому вы можете получить:
bool valid = false;
if (bValue1)
{
if (bValue2 && bValue3)
{
if (bValue4)
valid = true; //scenario 1
else if (!bValue4)
valid = true; //scenario 2
}
else if (!bValue2 && !bValue3 && !bValue4)
valid = true; //scenario 3
}
Более того, теперь вы можете ясно видеть, что bValue2 и bValue3 в некоторой степени связаны - вы можете извлечь их состояние в некоторые внешние функции или переменные с более подходящим именем (хотя это не всегда легко или уместно):
bool valid = false;
if (bValue1)
{
bool bValue1and2 = bValue1 && bValue2;
bool notBValue1and2 = !bValue2 && !bValue3;
if (bValue1and2)
{
if (bValue4)
valid = true; //scenario 1
else if (!bValue4)
valid = true; //scenario 2
}
else if (notBValue1and2 && !bValue4)
valid = true; //scenario 3
}
У этого способа есть свои преимущества и недостатки:
Если вы прогнозируете, что в приведенную выше логику произойдут изменения, вам следует использовать более простой подход, представленный @ gian-paolo.
В противном случае, если эти условия четко установлены и представляют собой своего рода «твердые правила», которые никогда не изменятся, рассмотрим мой последний фрагмент кода.
Я бы также использовал быстрые переменные для ясности. Как отмечалось ранее, сценарий 1 равен сценарию 2, поскольку значение bValue4 не влияет на истинность этих двух сценариев.
bool MAJORLY_TRUE=bValue1 && bValue2 && bValue3
bool MAJORLY_FALSE=!(bValue2 || bValue3 || bValue4)
тогда ваше выражение выглядит следующим образом:
if (MAJORLY_TRUE || (bValue1 && MAJORLY_FALSE))
{
// do something
}
else
{
// There is some error
}
Присвоение значимых имен переменным MAJORTRUE и MAJORFALSE (а также фактически переменным bValue *) очень поможет с удобочитаемостью и обслуживанием.
Сосредоточьтесь на удобочитаемости проблемы, а не на конкретном утверждении «если».
Хотя это приведет к появлению большего количества строк кода, и некоторые могут посчитать это излишним или ненужным. Я бы предположил, что абстрагирование ваших сценариев от конкретных логических значений - лучший способ сохранить удобочитаемость.
Разделив вещи на классы (не стесняйтесь просто использовать функции или любой другой инструмент, который вы предпочитаете) с понятными именами, мы можем намного легче показать значения, стоящие за каждым сценарием. Что еще более важно, в системе с множеством движущихся частей ее легче поддерживать и присоединять к вашим существующим системам (опять же, несмотря на то, сколько дополнительного кода задействовано).
#include <iostream>
#include <vector>
using namespace std;
// These values would likely not come from a single struct in real life
// Instead, they may be references to other booleans in other systems
struct Values
{
bool bValue1; // These would be given better names in reality
bool bValue2; // e.g. bDidTheCarCatchFire
bool bValue3; // and bDidTheWindshieldFallOff
bool bValue4;
};
class Scenario
{
public:
Scenario(Values& values)
: mValues(values) {}
virtual operator bool() = 0;
protected:
Values& mValues;
};
// Names as examples of things that describe your "scenarios" more effectively
class Scenario1_TheCarWasNotDamagedAtAll : public Scenario
{
public:
Scenario1_TheCarWasNotDamagedAtAll(Values& values) : Scenario(values) {}
virtual operator bool()
{
return mValues.bValue1
&& mValues.bValue2
&& mValues.bValue3
&& mValues.bValue4;
}
};
class Scenario2_TheCarBreaksDownButDidntGoOnFire : public Scenario
{
public:
Scenario2_TheCarBreaksDownButDidntGoOnFire(Values& values) : Scenario(values) {}
virtual operator bool()
{
return mValues.bValue1
&& mValues.bValue2
&& mValues.bValue3
&& !mValues.bValue4;
}
};
class Scenario3_TheCarWasCompletelyWreckedAndFireEverywhere : public Scenario
{
public:
Scenario3_TheCarWasCompletelyWreckedAndFireEverywhere(Values& values) : Scenario(values) {}
virtual operator bool()
{
return mValues.bValue1
&& !mValues.bValue2
&& !mValues.bValue3
&& !mValues.bValue4;
}
};
Scenario* findMatchingScenario(std::vector<Scenario*>& scenarios)
{
for(std::vector<Scenario*>::iterator it = scenarios.begin(); it != scenarios.end(); it++)
{
if (**it)
{
return *it;
}
}
return NULL;
}
int main() {
Values values = {true, true, true, true};
std::vector<Scenario*> scenarios = {
new Scenario1_TheCarWasNotDamagedAtAll(values),
new Scenario2_TheCarBreaksDownButDidntGoOnFire(values),
new Scenario3_TheCarWasCompletelyWreckedAndFireEverywhere(values)
};
Scenario* matchingScenario = findMatchingScenario(scenarios);
if (matchingScenario)
{
std::cout << matchingScenario << " was a match" << std::endl;
}
else
{
std::cout << "No match" << std::endl;
}
// your code goes here
return 0;
}
В какой-то момент многословие начинает ухудшать удобочитаемость. Я думаю, это заходит слишком далеко.
@JollyJoker Я действительно согласен с этой конкретной ситуацией - однако, мое внутреннее ощущение от того, как OP назвал все в очень общем виде, заключается в том, что их «настоящий» код, вероятно, намного сложнее, чем приведенный ими пример. На самом деле, я просто хотел представить эту альтернативу, так как я бы структурировал ее для чего-то гораздо более сложного / запутанного. Но вы правы - для конкретного примера OP это слишком многословно и усугубляет ситуацию.
Я даю здесь только свой ответ, поскольку в комментариях кто-то предложил показать мое решение. Я хочу поблагодарить всех за их понимание.
В итоге я решил добавить три новых "сценарных" метода boolean:
bool CChristianLifeMinistryValidationDlg::IsFirstWeekStudentItems(CChristianLifeMinistryEntry *pEntry)
{
return (INCLUDE_ITEM1(pEntry) &&
!INCLUDE_ITEM2(pEntry) &&
!INCLUDE_ITEM3(pEntry) &&
!INCLUDE_ITEM4(pEntry));
}
bool CChristianLifeMinistryValidationDlg::IsSecondWeekStudentItems(CChristianLifeMinistryEntry *pEntry)
{
return (INCLUDE_ITEM1(pEntry) &&
INCLUDE_ITEM2(pEntry) &&
INCLUDE_ITEM3(pEntry) &&
INCLUDE_ITEM4(pEntry));
}
bool CChristianLifeMinistryValidationDlg::IsOtherWeekStudentItems(CChristianLifeMinistryEntry *pEntry)
{
return (INCLUDE_ITEM1(pEntry) &&
INCLUDE_ITEM2(pEntry) &&
INCLUDE_ITEM3(pEntry) &&
!INCLUDE_ITEM4(pEntry));
}
Затем я смог применить эти мои процедуры проверки следующим образом:
if (!IsFirstWeekStudentItems(pEntry) && !IsSecondWeekStudentItems(pEntry) && !IsOtherWeekStudentItems(pEntry))
{
; Error
}
В моем живом приложении 4 значения bool фактически извлекаются из DWORD, в котором закодированы 4 значения.
Еще раз спасибо всем.
Спасибо, что поделились решением. :) На самом деле это лучше, чем сложные адские условия. Может быть, вы все еще можете назвать INCLUDE_ITEM1 и т. д. Лучше, и у вас все в порядке. :)
@HardikModha Ну, технически это «предметы для учащихся», и флаг должен указывать, должны ли они быть «включены». Так что я думаю, что название, хотя и звучит обобщенно, на самом деле имеет значение в этом контексте. :)
Мой предыдущий ответ уже является принятым ответом, я добавляю здесь кое-что, что я считаю читабельным, простым и в этом случае открытым для будущих модификаций:
Начиная с ответа @ZdeslavVojkovic (который я считаю неплохим), я придумал следующее:
#include <iostream>
#include <set>
//using namespace std;
int GetScenarioInt(bool bValue1, bool bValue2, bool bValue3, bool bValue4)
{
return bValue1 << 3 | bValue2 << 2 | bValue3 << 1 | bValue4;
}
bool IsValidScenario(bool bValue1, bool bValue2, bool bValue3, bool bValue4)
{
std::set<int> validScenarios;
validScenarios.insert(GetScenarioInt(true, true, true, true));
validScenarios.insert(GetScenarioInt(true, true, true, false));
validScenarios.insert(GetScenarioInt(true, false, false, false));
int currentScenario = GetScenarioInt(bValue1, bValue2, bValue3, bValue4);
return validScenarios.find(currentScenario) != validScenarios.end();
}
int main()
{
std::cout << IsValidScenario(true, true, true, false) << "\n"; // expected = true;
std::cout << IsValidScenario(true, true, false, false) << "\n"; // expected = false;
return 0;
}
Увидеть в работе здесь
Что ж, это «элегантное и удобное в обслуживании» (IMHO) решение, к которому я обычно стремлюсь, но на самом деле, для случая OP мой предыдущий ответ «если» лучше соответствует требованиям OP, даже если он не изящный и не обслуживаемый.
Вы знаете, что всегда можете отредактировать свой предыдущий ответ и внести улучшения.
Это зависит от того, что они представляют.
Например, если 1 - это ключ, а 2 и 3 - два человека, которые должны согласиться (кроме случаев, когда они согласны с NOT, им требуется третье лицо - 4 - для подтверждения), наиболее читаемым может быть:
1 &&
(
(2 && 3)
||
((!2 && !3) && !4)
)
по многочисленным просьбам:
Key &&
(
(Alice && Bob)
||
((!Alice && !Bob) && !Charlie)
)
Возможно, вы правы, но использование цифр для иллюстрации вашей точки зрения отвлекает от вашего ответа. Попробуйте использовать описательные имена.
@jxh Это числа, используемые OP. Я только что удалил bValue.
@jxh Надеюсь, теперь стало лучше.
Небольшое изменение прекрасного ответа @GianPaolo, которое некоторым может быть легче прочитать:
bool any_of_three_scenarios(bool v1, bool v2, bool v3, bool v4)
{
return (v1 && v2 && v3 && v4) // scenario 1
|| (v1 && v2 && v3 && !v4) // scenario 2
|| (v1 && !v2 && !v3 && !v4); // scenario 3
}
if (any_of_three_scenarios(bValue1,bValue2,bValue3,bValue4))
{
// ...
}
Я не вижу никаких ответов, в которых говорится о том, чтобы назвать сценарии, хотя решение OP делает именно это.
Для меня лучше всего инкапсулировать комментарий каждого сценария либо в имя переменной, либо в имя функции. Вы с большей вероятностью проигнорируете комментарий, чем имя, и если ваша логика изменится в будущем, вы с большей вероятностью измените имя, чем комментарий. Вы не можете реорганизовать комментарий.
Если вы планируете повторно использовать эти сценарии вне своей функции (или можете захотеть это сделать), создайте функцию, которая сообщает, что она оценивает (constexpr / noexcept необязательно, но рекомендуется):
constexpr bool IsScenario1(bool b1, bool b2, bool b3, bool b4) noexcept
{ return b1 && b2 && b3 && b4; }
constexpr bool IsScenario2(bool b1, bool b2, bool b3, bool b4) noexcept
{ return b1 && b2 && b3 && !b4; }
constexpr bool IsScenario3(bool b1, bool b2, bool b3, bool b4) noexcept
{ return b1 && !b2 && !b3 && !b4; }
Если возможно, сделайте эти методы класса (как в решении OP). Вы можете использовать переменные внутри своей функции, если не думаете, что будете повторно использовать логику:
const auto is_scenario_1 = bValue1 && bValue2 && bValue3 && bValue4;
const auto is_scenario_2 = bvalue1 && bvalue2 && bValue3 && !bValue4;
const auto is_scenario_3 = bValue1 && !bValue2 && !bValue3 && !bValue4;
Компилятор, скорее всего, решит, что если bValue1 ложно, то все сценарии ложны. Не беспокойтесь о том, чтобы сделать его быстрым, просто правильным и читаемым. Если вы профилируете свой код и обнаруживаете, что это узкое место, потому что компилятор сгенерировал неоптимальный код с -O2 или выше, попробуйте переписать его.
Мне это нравится немного больше, чем решение Джана Паоло (уже хорошее): оно позволяет избежать потока управления и использования перезаписываемой переменной - более функциональный стиль.
Каждый ответ слишком сложен и труден для чтения. Лучшее решение - заявление switch(). Он удобен для чтения и упрощает добавление / изменение дополнительных случаев. Компиляторы также хороши в оптимизации операторов switch().
switch( (bValue4 << 3) | (bValue3 << 2) | (bValue2 << 1) | (bValue1) )
{
case 0b1111:
// scenario 1
break;
case 0b0111:
// scenario 2
break;
case 0b0001:
// scenario 3
break;
default:
// fault condition
break;
}
Вы, конечно, можете использовать константы и OR их вместе в операторах case для еще большей читабельности.
Будучи старым программистом на C, я бы определил макрос «PackBools» и использовал бы его как для «switch (PackBools (a, b, c, d))», так и для случаев, например, либо напрямую »case PackBools (true , true ...) "или определите их как локальные константы. например "const беззнаковый int сценарий1 = PackBools (истина, истина ...);"
Я бы стремился к простоте и удобочитаемости.
bool scenario1 = bValue1 && bValue2 && bValue3 && bValue4;
bool scenario2 = bValue1 && bValue2 && bValue3 && !bValue4;
bool scenario3 = bValue1 && !bValue2 && !bValue3 && !bValue4;
if (scenario1 || scenario2 || scenario3) {
// Do whatever.
}
Обязательно замените названия сценариев, а также названия флагов чем-нибудь описательным. Если это имеет смысл для вашей конкретной проблемы, вы можете рассмотреть эту альтернативу:
bool scenario1or2 = bValue1 && bValue2 && bValue3;
bool scenario3 = bValue1 && !bValue2 && !bValue3 && !bValue4;
if (scenario1or2 || scenario3) {
// Do whatever.
}
Здесь важна не логика предикатов. Он описывает ваш домен и ясно выражает ваши намерения. Ключевым моментом здесь является дать хорошие имена всем входным параметрам и промежуточным переменным. Если вы не можете найти подходящие имена переменных, это может быть признаком того, что вы неправильно описываете проблему.
+1 Я бы тоже так поступил. Как указывает @RedFilter, и в отличие от принятого ответа, это самодокументируется. Присвоение сценариям собственных имен на отдельном шаге гораздо удобнее для чтения.
используйте битовое поле:
unoin {
struct {
bool b1: 1;
bool b2: 1;
bool b3: 1;
bool b4: 1;
} b;
int i;
} u;
// set:
u.b.b1=true;
...
// test
if (u.i == 0x0f) {...}
if (u.i == 0x0e) {...}
if (u.i == 0x08) {...}
PS:
Это большая жалость для CPP. Но UB меня не беспокоит, проверьте это на http://coliru.stacked-crooked.com/a/2b556abfc28574a1.
Это вызывает UB из-за доступа к неактивному полю объединения.
Формально это UB в C++, вы не можете установить один член объединения и читать из другого. Технически было бы лучше реализовать шаблонные геттеры \ сеттеры для битов целого значения.
Я думаю, что поведение изменилось бы на «Определенное реализацией», если бы адрес объединения был преобразован в unsigned char*, хотя я думаю, что простое использование чего-то вроде ((((flag4 <<1) | flag3) << 1) | flag2) << 1) | flag1, вероятно, было бы более эффективным.
Во-первых, предполагая, что вы можете только изменить проверку сценария, я бы сосредоточился на удобочитаемости и просто заключил проверку в функцию, чтобы вы могли просто вызвать if (ScenarioA()).
Теперь, предполагая, что вы действительно хотите / должны оптимизировать это, я бы рекомендовал преобразовать тесно связанные логические значения в постоянные целые числа и использовать для них битовые операторы.
public class Options {
public const bool A = 2; // 0001
public const bool B = 4; // 0010
public const bool C = 16;// 0100
public const bool D = 32;// 1000
//public const bool N = 2^n; (up to n=32)
}
...
public isScenario3(int options) {
int s3 = Options.A | Options.B | Options.C;
// for true if only s3 options are set
return options == s3;
// for true if s3 options are set
// return options & s3 == s3
}
Это делает выражение сценария таким же простым, как перечисление того, что является его частью, позволяет использовать оператор switch для перехода к правильному условию и сбивать с толку коллег-разработчиков, которые не видели этого раньше. (C# RegexOptions использует этот шаблон для установки флагов, я не знаю, есть ли пример библиотеки C++)
На самом деле я использую не четыре логических значения, а DWORD с четырьмя встроенными BOOLS. Слишком поздно менять это сейчас. Но спасибо за ваше предложение.
Подумайте о том, чтобы максимально прямо перевести ваши таблицы в вашу программу. Управляйте программой, основываясь на таблице, вместо того, чтобы имитировать ее с помощью логики.
template<class T0>
auto is_any_of( T0 const& t0, std::initializer_list<T0> il ) {
for (auto&& x:il)
if (x==t0) return true;
return false;
}
в настоящее время
if (is_any_of(
std::make_tuple(bValue1, bValue2, bValue3, bValue4),
{
{true, true, true, true},
{true, true, true, false},
{true, false, false, false}
}
))
это, насколько возможно, напрямую кодирует вашу таблицу истинности в компилятор.
Вы также можете использовать std::any_of напрямую:
using entry = std::array<bool, 4>;
constexpr entry acceptable[] =
{
{true, true, true, true},
{true, true, true, false},
{true, false, false, false}
};
if (std::any_of( begin(acceptable), end(acceptable), [&](auto&&x){
return entry{bValue1, bValue2, bValue3, bValue4} == x;
}) {
}
компилятор может встроить код, исключить любую итерацию и построить для вас собственную логику. Между тем, ваш код в точности отражает ваше понимание проблемы.
Первую версию так легко читать и поддерживать, что она мне очень нравится. Второй труднее читать, по крайней мере, для меня, и для него требуется уровень навыков C++, может быть, выше среднего, наверняка выше моего. Не все умеют писать. Только что узнал что-то новое, спасибо
If (!bValue1 || (bValue2 != bValue3) || (!bValue4 && bValue2))
{
// you have a problem
}
просто
Побитовая операция выглядит очень чисто и понятно.
int bitwise = (bValue4 << 3) | (bValue3 << 2) | (bValue2 << 1) | (bValue1);
if (bitwise == 0b1111 || bitwise == 0b0111 || bitwise == 0b0001)
{
//satisfying condition
}
Поразрядный сравнение мне кажется читаемым. Композиция же выглядит искусственно.
Некоторым людям будет легче читать вложенные if. Вот моя версия
bool check(int bValue1, int bValue2, int bValue3, int bValue4)
{
if (bValue1)
{
if (bValue2)
{
// scenario 1-2
return bValue3;
}
else
{
// scenario 3
return !bValue3 && !bValue4;
}
}
return false;
}
Лично я обычно избегаю вложенных операторов if, если это возможно. Хотя этот случай приятный и читаемый, как только добавляются новые возможности, вложение может стать очень трудночитаемым. Но если сценарии никогда не меняются, это определенно хорошее и удобочитаемое решение.
@ Dnomyar96 согласен. Лично я тоже избегаю вложенных «если». Иногда, если логика сложна, мне легче понять логику, разбив ее на части. Например, когда вы входите в блок bValue1, вы можете рассматривать все в нем как новую свежую страницу в своем умственном процессе. Готов поспорить, подход к проблеме может быть очень личным или даже культурным.
Мои 2 цента: объявите переменную sum (integer), чтобы
if (bValue1)
{
sum=sum+1;
}
if (bValue2)
{
sum=sum+2;
}
if (bValue3)
{
sum=sum+4;
}
if (bValue4)
{
sum=sum+8;
}
Проверьте sum на соответствие условиям, которые вам нужны, и все.
Таким образом, вы можете легко добавить больше условий в будущем, чтобы его было легко читать.
Вам не придется беспокоиться о недопустимых комбинациях логических флагов, если вы избавитесь от логических флагов.
The acceptable values are:
Scenario 1 | Scenario 2 | Scenario 3 bValue1: true | true | true bValue2: true | true | false bValue3: true | true | false bValue4: true | false | false
У вас явно есть три состояния (сценария). Было бы лучше смоделировать это и выводить логических свойств из этих состояний, а не наоборот.
enum State
{
scenario1,
scenario2,
scenario3,
};
inline bool isValue1(State s)
{
// (Well, this is kind of silly. Do you really need this flag?)
return true;
}
inline bool isValue2(State s)
{
switch (s)
{
case scenario1:
case scenario2:
return true;
case scenario3:
return false;
}
}
inline bool isValue3(State s)
{
// (This is silly too. Do you really need this flag?)
return isValue2(s);
}
inline bool isValue4(State s)
{
switch (s)
{
case scenario1:
return true;
case scenario2:
case scenario3:
return false;
}
}
Это определенно больше кода, чем в Ответ Джан Паоло, но в зависимости от вашей ситуации это могло бы быть намного проще в обслуживании:
enum в операторах switch перехватит средства получения свойств, которые не обрабатывают этот сценарий.У этого подхода также есть побочное преимущество - он очень эффективен.
Просто личное предпочтение перед принятым ответом, но я бы написал:
bool valid = false;
// scenario 1
valid = valid || (bValue1 && bValue2 && bValue3 && bValue4);
// scenario 2
valid = valid || (bValue1 && bValue2 && bValue3 && !bValue4);
// scenario 3
valid = valid || (bValue1 && !bValue2 && !bValue3 && !bValue4);
На этот вопрос было дано несколько правильных ответов, но я бы придерживался другой точки зрения: если код выглядит слишком сложным, что-то не так. Код будет сложно отлаживать, и, скорее всего, он будет «одноразовым».
В реальной жизни, когда мы находим такую ситуацию:
Scenario 1 | Scenario 2 | Scenario 3
bValue1: true | true | true
bValue2: true | true | false
bValue3: true | true | false
bValue4: true | false | false
Когда четыре состояния связаны таким точным шаблоном, мы имеем дело с конфигурацией некоторой "сущности" в нашей модели.
Крайняя метафора - это то, как мы бы описали «людей» в модели, если бы мы не знали об их существовании как единых сущностях с компонентами, связанными с определенными степенями свободы: нам пришлось бы описывать независимые состояния «туловища», «руки», «ноги» и «голова», которые усложнили бы понимание описанной системы. Непосредственным результатом были бы неестественно сложные логические выражения.
Очевидно, что способ уменьшить сложность - это абстракция, а предпочтительным инструментом в C++ является объектная парадигма.
Так вот вопрос: Зачем есть такая закономерность? Что это такое и что из себя представляет?
Поскольку мы не знаем ответа, мы можем прибегнуть к математической абстракции: множество: у нас есть три сценария, каждый из которых теперь является массивом.
0 1 2 3
Scenario 1: T T T T
Scenario 2: T T T F
Scenario 3: T F F F
На этом этапе у вас есть исходная конфигурация. как массив. Например. std::array имеет оператор равенства:
В этот момент ваш синтаксис становится:
if ( myarray == scenario1 ) {
// arrays contents are the same
}
else if ( myarray == scenario2 ) {
// arrays contents are the same
}
else if ( myarray == scenario3 ) {
// arrays contents are the same
}
else {
// not the same
}
Как и ответ Джан Паоло, он короткий, ясный и легко проверяемый / отлаживаемый. В этом случае мы делегировали детали логических выражений компилятору.
Принятый ответ хорош, если у вас всего 3 случая и логика для каждого проста.
Но если логика для каждого случая была более сложной или случаев намного больше, гораздо лучшим вариантом было бы использовать шаблон проектирования цепочка ответственности.
Вы создаете BaseValidator, который содержит ссылку на BaseValidator и метод на validate, а также метод для вызова проверки на указанном валидаторе.
class BaseValidator {
BaseValidator* nextValidator;
public:
BaseValidator() {
nextValidator = 0;
}
void link(BaseValidator validator) {
if (nextValidator) {
nextValidator->link(validator);
} else {
nextValidator = validator;
}
}
bool callLinkedValidator(bool v1, bool v2, bool v3, bool v4) {
if (nextValidator) {
return nextValidator->validate(v1, v2, v3, v4);
}
return false;
}
virtual bool validate(bool v1, bool v2, bool v3, bool v4) {
return false;
}
}
Затем вы создаете несколько подклассов, которые наследуются от BaseValidator, заменяя метод validate логикой, необходимой для каждого валидатора.
class Validator1: public BaseValidator {
public:
bool validate(bool v1, bool v2, bool v3, bool v4) {
if (v1 && v2 && v3 && v4) {
return true;
}
return nextValidator->callLinkedValidator(v1, v2, v3, v4);
}
}
Затем использовать его просто: создайте экземпляр каждого из ваших валидаторов и установите каждый из них как корень для других:
Validator1 firstValidator = new Validator1();
Validator2 secondValidator = new Validator2();
Validator3 thirdValidator = new Validator3();
firstValidator.link(secondValidator);
firstValidator.link(thirdValidator);
if (firstValidator.validate(value1, value2, value3, value4)) { ... }
По сути, каждый случай валидации имеет свой собственный класс, который отвечает за (а) определение, соответствует ли валидация случаю тот, и (б) отправку валидации кому-то еще в цепочке, если это не так.
Обратите внимание, что я не знаком с C++. Я попытался сопоставить синтаксис с некоторыми примерами, которые нашел в Интернете, но если это не сработает, относитесь к нему как к псевдокоду. У меня также есть полный рабочий пример Python ниже, который при желании можно использовать в качестве основы.
class BaseValidator:
def __init__(self):
self.nextValidator = 0
def link(self, validator):
if (self.nextValidator):
self.nextValidator.link(validator)
else:
self.nextValidator = validator
def callLinkedValidator(self, v1, v2, v3, v4):
if (self.nextValidator):
return self.nextValidator.validate(v1, v2, v3, v4)
return False
def validate(self, v1, v2, v3, v4):
return False
class Validator1(BaseValidator):
def validate(self, v1, v2, v3, v4):
if (v1 and v2 and v3 and v4):
return True
return self.callLinkedValidator(v1, v2, v3, v4)
class Validator2(BaseValidator):
def validate(self, v1, v2, v3, v4):
if (v1 and v2 and v3 and not v4):
return True
return self.callLinkedValidator(v1, v2, v3, v4)
class Validator3(BaseValidator):
def validate(self, v1, v2, v3, v4):
if (v1 and not v2 and not v3 and not v4):
return True
return self.callLinkedValidator(v1, v2, v3, v4)
firstValidator = Validator1()
secondValidator = Validator2()
thirdValidator = Validator3()
firstValidator.link(secondValidator)
firstValidator.link(thirdValidator)
print(firstValidator.validate(False, False, True, False))
Опять же, вы можете найти это излишество для своего конкретного примера, но он создает гораздо более чистый код, если вы в конечном итоге столкнетесь с гораздо более сложным набором случаев, которые необходимо выполнить.
if (!bValue1)
return false;
if (bValue2 != bValue3)
return false;
if (bValue3 == false && bValuer4 == true)
return false;
return true;
Я бы использовал таблицу вместо сложного оператора
if. Кроме того, поскольку это логические флаги, вы можете моделировать каждый сценарий как константу и проверять его.