Предыдущая часть:
Сегодня рассмотрим сказку "Колобок". Мучное изделие, убежавшее от бабушки и дедушки, перечисляет встреченным персонажам, от кого оно уже успело уйти. Ситуация складывается такая же, как в Теремке: если Колобок ушёл от бесконечного количества посягателей, то на их перечисление он должен затратить бесконечное время.
Но так как всё это уже было в Теремке, в этот раз рассмотрим другое.
Описать путь Колобка можно следующим образом:
- Уйти от бабушки и дедушки
- Уйти от зайца
- Уйти от волка
- и т.д.
Эти события происходят в линейной последовательности, то есть мы можем программно записать их в виде таких же последовательных вызовов функций:
escape_grandparents();
escape_hare();
escape_wolf();
и т.д.
Каждая такая функция внутри себя содержит взаимодействие Колобка с очередным персонажем – поэтому они называются по-разному.
Но мы видим, что функции по сути одни и те же, меняются только персонажи. Их все можно заменить одной функцией с передачей в неё параметра:
escape('grandparents');
escape('hare');
escape('wolf');
Тело функции можно описать так:
Но как мы знаем, бегство не всегда заканчивается успешно, поэтому мы должны ввести в функцию дополнительное условие для лисы, а также вернуть результат "успех" или "неуспех".
Теперь при каждом вызове функции escape() нужно проверять, какой результат она вернула: если true, то Колобок катится дальше, иначе сказка заканчивается.
Замечу, что выражение escape() == true можно заменить на просто escape(), а escape() == false на !escape(), так проще.
и т.д.
Можно инвертировать условия (то есть проверять противоположный результат). И тогда получим следующий код:
Оба варианта идентичны и скорее всего даже компилироваться будут в одинаковый машинный код. Но к различиям между ними мы ещё вернёмся.
Пока обратим внимание на то, что у сказки один (плохой) конец, но так как он может произойти в любой момент, мы вынуждены обрабатывать его после каждой встречи, вставляя вызов функции die() – это стандартная функция в PHP, которая прерывает выполнение программы так же, как прерывается жизнь Колобка, то есть после die() никакой код выполняться уже не будет.
Мы можем использовать механизм исключений, чтобы сделать код более стройным. Для этого функция должна бросать исключение:
А снаружи будем его ловить. Проверять результат после каждого вызова функции уже не надо:
Про исключения более подробно написано здесь:
Можно добиться аналогичного результата и без исключений. Вернёмся к варианту функции, который возвращает успех или неуспех, и будем как и раньше проверять его. Но теперь добавим переменную $escaped, которая будет отвечать за общий результат – удалось Колобку выжить в пределе или нет. Изначально она будет равна false, так как будущее Колобка мы ещё не знаем. А однозначно сказать можно будет только после лисы, поэтому код получается такой:
А также можно переписать данные условия в виде логического произведения результатов функций:
Ранее я упоминал об идентичности двух вариантов кода с условиями, но здесь уже возникает разница. Напомню, первый вариант выглядит так:
Здесь видно, что переписать его аналогичным образом не получится. Например, если ввести такую же переменную $escaped (с учётом инверсии условий она будет равна изначально true), то получим:
Здесь мы также имеем один финал, но теперь в каждом условии нужно менять значение $escaped, то есть от множественности действий мы не избавились.
Кроме того, так как программа перестала умирать сразу же, функции продолжат вызываться, даже если Колобок был уже съеден, допустим, зайцем, а окончательное решение будет известно только в конце.
Значит, нужно дополнительно проверять, чем закончилось предыдущее условие:
Что опять же лишь усложняет.
В качестве компромисса можно использовать такой код:
Конструкция $escaped && ... сработает только в том случае, когда $escaped == true. Иначе значение выражения в любом случае будет false, поэтому до вызова функции escape() оно даже не доходит (нет смысла).
И хотя побочные расходы сведены к минимуму, всё равно мы обязаны пройти по каждой строчке для каждого персонажа и попытаться выполнить её, даже если Колобок уже был съеден кем-то раньше.
Это происходит потому, что события встречи с зайцем, волком и т.д. не связаны друг с другом и идут просто по порядку.
В варианте с вложенными условиями каждое следующее условие зависит от предыдущего. Колобок, убежав от бабушки и дедушки, попадает в новый контекст, в котором он ушёл от бабушки и дедушки. В этом контексте он может уйти от зайца. А уйдя от зайца, попадает в новый контекст, в котором он ушёл от зайца и может уйти от волка, и т.д. Если же его постигла неудача, он не попадает в следующий контекст. Это будущее, которое уже навсегда закрыто для него, и которое можно даже не проверять.
Это отражено в самом коде, когда мы используем отступы. Каждый отступ – новый контекст, новая реальность для Колобка.
Но что делать, если персонажей 1000, ведь будет абсурдом писать код с 1000 уровней вложенности? Хуже того, количество персонажей может быть вообще неизвестным заранее.
Что ж, на помощь придёт как всегда перебор массива в цикле: