• Введение
  • Что такое конечный автомат?
  • Явно и неявно определенные конечные автоматы
  • Подход 1: зависящее от специфики конкретной ситуации, децентрализованное, неявное управление состояниями (неудачный подход)
  • Подход 2: плановое, централизованное, явное управление состояниями (удачный подход)
  • Сколько конечных автоматов должно быть в приложении?
  • Конечный автомат для пользовательского интерфейса
  • Конечный автомат для модели памяти
  • Конечный автомат для фоновых задач
  • Использование конечных автоматов в играх
  • Резюме 
  • ГЛАВА 5

    Наш друг конечный автомат

    state [`stAt]

    Функция: существительное

    Использование: часто в качестве определения

    Этимология: средневек. англ. stat, от устар. фр. и лат.; устар. фр. estat, от лат. status, между stare и stand — ближе к STAND

    Дата: 13 столетие

    : режим или условие нахождения <в состоянии готовности> b (1): настроение ума или темперамент <во взвинченном состоянии> (2) пребывание в необычном напряжении или волнении

    : период или стадия физического существования чего-либо

    ((www.m-w.com, 2004))

    ma-chine [mashEn]

    сущ. (мн. ma-chines)

    Использование: часто в качестве определения

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

    стиральная машина

    ((Encarta 2004, Quotations))

    Введение

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

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

    Что такое конечный автомат?

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

    "Состояние" вашего приложения может быть определено как совокупность значений всех его переменных. Значения всех переменных приложения представляют уникальное состояние, которое изменится лишь в результате воздействия какого-либо внешнего события. Примерами подобного рода событий, способных изменить состояние приложения, являются такие события, как нажатие кнопки пользователем, событие, запускаемое при получении сообщения, или же событие таймера. В любой момент времени ваше приложение находится в каком-то определенном состоянии. В результате возмущения этого состояния приложение переходит в другое состояние. Для определения следующего состояния вашего приложения используется его текущее состояние плюс новые входные данные.

    Конечный автомат является средством формальной структуризации этой реальности. Вместо того чтобы использовать все переменные приложения в качестве расширенного определения его состояния, конечный автомат создает единственную переменную, в которой хранится информация о некоем важном аспекте состояния приложения. Такой переменной обычно является элемент перечисления (например, int m_myApplicationState) некоторого множества действительных состояний, определяемого глобально или на уровне класса. Мощь подхода, использующего конечный автомат, обусловлена тем, что он позволяет в явном виде определить действительные состояния для некоторого аспекта вашего приложения и задать соответствующие варианты поведения при переходах приложения из одного состояния в другое.

    У приложения может быть несколько конечных автоматов, для каждого из которых определяется собственный набор вариантов поведения приложения, подлежащих структуризации. Приложение, для которого конечные автоматы не определены, в действительности является приложением с множеством конечных автоматов; каждая переменная, определенная на уровне приложения, по сути дела сама является конечным автоматом, и любой код может получить доступ к состоянию и изменить его. Конечные автоматы используются для формирования набора взаимосвязанных переменных или вариантов поведения и их логической организации, облегчающей обработку состояний. Конечные автоматы — суть организованное поведение.

    На рис. 5.1 в схематическом виде представлен конечный автомат обучающей игры, позволяющей изучать иностранные слова путем выбора одного из предлагаемых вариантов ответа. Поведение приложения разбито на пять логических состояний. Эти логические состояния существуют независимо от того, будут ли они выбраны нами для введения их определений в конечный автомат. Конечный автомат — это просто формальное описание того, как работает приложение. Она позволяет нам ясно представлять себе различные состояния, в которых может находиться приложение в процессе взаимодействия с ним пользователя.

    Другим способом представления состояний приложения является построение диаграммы переходов. Эта диаграмма содержит список дискретных состояний, в которых может находиться приложение, а также возможные варианты переходов между состояниями. Список возможных состояний и вариантов перехода из одного состояния в другое для нашего приложения представлен в табл. 5.1. 

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


    Таблица 5.1. Варианты изменения состояний для словарной игры с множественным выбором

    Состояние Внешний ввод Следующее состояние
    StartScreen Пользователь выбрал вариант "Next Question" AskQuestion
    StartScreen Пользователь выбрал вариант "Correct Answer" Запрещенный переход!
    StartScreen Пользователь выбрал вариант "Incorrect Answer" Запрещенный переход!
    StartScreen Пользователь выбрал вариант "End Game" Запрещенный переход!
    AskQuestion Пользователь выбрал вариант "Next Question" Запрещенный переход!
    AskQuestion Пользователь выбрал вариант "Correct Answer" CongratulateUser
    AskQuestion Пользователь выбрал вариант "Incorrect Answer" ScoldUser
    AskQuestion Пользователь выбрал вариант "End Game" Запрещенный переход!
    CongratulateUser Пользователь выбрал вариант "Next Question" AskQuestion
    CongratulateUser Пользователь выбрал вариант "Correct Answer" Запрещенный переход!
    CongratulateUser Пользователь выбрал вариант "Incorrect Answer" Запрещенный переход!
    CongratulateUser Пользователь выбрал вариант "End Game" StartScreen
    ScoldUser Пользователь выбрал вариант "Next Question" AskQuestion
    ScoldUser Пользователь выбрал вариант "Correct Answer" Запрещенный переход!
    ScoldUser Пользователь выбрал вариант "Incorrect Answer" Запрещенный переход!
    ScoldUser Пользователь выбрал вариант "End Game" StartScreen
    НА ЗАМЕТКУ

    Я решил привести в таблице все возможные перестановки переменных, указанных в столбцах "Состояние" и "Внешний ввод", чтобы проиллюстрировать тот факт, что не любое изменение состояния разрешено. Переходы между состояниями, указанными в строках, для которых в столбце "Следующее состояние" содержится пояснение "Запрещенный переход!", недопустимы в нашем приложении. Если приложение пытается каким-либо образом осуществить такое изменение состояния, значит, в логике его выполнения присутствуют ошибки. В случае если предпринимается попытка выполнения недействительного перехода, логика конечного автомата должна возбуждать исключение или, по крайней мере, использовать оператор ASSERT в режиме отладки. Явная идентификация запрещенных переходов между состояниями облегчает отладку приложений.

    В листинге 5.1 представлен код, реализующий определенный выше конечный автомат. Этот код соответствует диаграмме переходов, представленной в табл. 5.1 и на рис. 5.1. Обратите внимание на то, что в приведенной ниже функции участки кода, соответствующие различным изменениям состояний, содержат вызовы функций, отключенные с помощью символов комментариев. Эти функциональные вызовы представляют ту часть работы, которая должна быть выполнена для соответствующего изменения состояния, и отключены символами комментариев с той целью, чтобы приведенный ниже код можно было компилировать как независимый блок; реализация вызываемых функций остается за вами. Для определения текущего варианта изменения состояния удобно использовать блок операторов switch/case.

    Каждый оператор case соответствует одному из вариантов изменения состояния и должен содержать вызов функции, выполняющей всю необходимую для этого работу. Подобного рода централизация и инкапсуляция управления состояниями является одним из наиболее мощных аспектов использования конечных автоматов; все важные изменения состояний приложения определяются и обрабатываются централизованно в одном месте программы.

    Листинг 5.1. Простой код конечного автомата для игры с множественным выбором

    class MyStateMachineClass {

     private enum GameState {

      StartScreen, AskQuestion, CongratulateUser, ScoldUser

     }

     private GameState m_CurrentGameState;

     //---------------------------------------------------------------------

     //Конечный автомат, воздействующий на пользовательский интерфейс

     //и управляющий переходами приложения в другие состояния в соответствии

     //c текущим режимом работы пользователя

     //---------------------------------------------------------------------

     private void StateChangeForGame(GameState newGameUIState) {

      //Определить, в какое состояние переходит приложение

      switch(newGameUIState) {

      case GameState.StartScreen:

       //Если переход в данное состояние осуществляется из состояния,

       //для которого это запрещено, возбудить исключение

       if ((m_CurrentGameState != GameState.CongratulateUser) && (m_CurrentGameState != GameState.ScoldUser)) {

        throw new System.Exception("Запрещённый переход!");

       }

       //ЧТО СДЕЛАТЬ: Поместите сюда код, выполняющий следующие операции:

       // 1. Скрытие (Hide), отображение (Show) и перемещение (Move)

       // элементов управления пользовательского интерфейса

       // 2. Настройка переменных/состояния игры, соответствующих

       // данному режиму работы

       //

       // SetUpGameStateForStartScreen();

       break;

      case GameState.AskQuestion:

       //Если переход в данное состояние осуществляется из состояния,

       //для которого это запрещено, возбудить исключение

       if ((m_CurrentGameState != GameState.StartScreen)

        && (m_CurrentGameState != GameState.CongratulateUser)

        && (m_CurrentGameState !=GameState.ScoldUser)) {

        throw new System.Exception("Запрещённый переход!");

       }

       //ЧТО СДЕЛАТЬ: Поместите сюда код, выполняющий следующие операции:

       // 1. Скрытие (Hide), отображение (Show) и перемещение (Move)

       // элементов управления пользовательского интерфейса

       // 2. Настройка переменных/состояния игры, соответствующих

       // данному режиму работы

       //

       // SetUpGameStateForAskQuestion();

       break;

      case GameState.CongratulateUser:

       //Если переход в данное состояние осуществляется из состояния,

       //для которого это запрещено, возбудить исключение

       if (m_CurrentGameState != GameState.AskQuestion) {

        throw new System.Exception("Запрещённый переход!");

       }

       //ЧТО СДЕЛАТЬ: Поместите сюда код, выполняющий следующие операции:

       // 1. Скрытие (Hide), отображение (Show) и перемещение (Move)

       // элементов управления пользовательского интерфейса

       // 2. Настройка переменных/состояния игры, соответствующих

       // данному режиму работы

       //

       // SetUpGameStateForCongratulateUser();

       break;

      case GameState.ScoldUser:

       //Если переход в данное состояние осуществляется из состояния,

       //для которого это запрещено, возбудить исключение

       if (m_CurrentGameState != GameState.AskQuestion) {

        throw new System.Exception("Запрещённый переход!");

       }

       //ЧТО СДЕЛАТЬ: Поместите сюда код, выполняющий следующие операции:

       // 1. Скрытие (Hide), отображение (Show) и перемещение (Move)

       // элементов управления пользовательского интерфейса

       // 2. Настройка переменных/состояния игры, соответствующих

       // данному режиму работы

       //

       // SetUpGameStateForScoldUser();

       break;

      default:

       throw new System.Exception("Неизвестное состояние!");

      }

      //Сохранить запрошенное новое состояние в качестве текущего

      m_CurrentGameState = newGameUIState;

     }

    } //Конец класса

    Явно и неявно определенные конечные автоматы

    Планируете ли вы это или не планируете, но ваш код будет так или иначе управляться состояниями. Например, если какой-либо элемент управления необходимо сделать недоступным для пользователя, то разработчики часто добиваются этого, устанавливая для свойств Enabled и Visible этих элементов управления значение false (например, TextBox1.Visible = false;). Для написания кода такого типа существует два возможных подхода, которые рассматриваются ниже

    Подход 1: зависящее от специфики конкретной ситуации, децентрализованное, неявное управление состояниями (неудачный подход)

    Специализированный стиль проектирования, ориентированный на максимально возможный учет специфики конкретной задачи, часто встречается в тех случаях, когда приложение в процессе разработки постепенно усложняется. Различные аспекты состояния приложения изменяются в разных местах приложения. Данные о состоянии хранятся в таких свойствах элементов управления, как Visible, Enabled, Size или Position. Переменные, используемые для хранения ключевой информации о состоянии, изменяются непосредственно в тех строках кода, где это оказывается необходимым, а загрузка данных и освобождение памяти от них распределяются по всему приложению в зависимости от конкретной ситуации. Развитие событий напоминает "перетягивание каната" между различными частями приложения, поскольку каждая из функций делает все необходимое для выполнения возложенных на нее задач, не обращая никакого внимания на остальную часть приложения. Простейшим примером подобного поведения может служить код, реагирующий на такие события пользовательского интерфейса, как щелчок на кнопке, которой соответствует встроенный код, изменяющий состояние приложения. В листинге 5.2 приведен типичный код, встречающийся в классах формы приложения, соответствующих описанному подходу. Как код события загрузки формы form1, так и код события щелчка кнопки button1 вносят изменения, которые влияют на общее состояние формы.

    Листинг 5.2. Неявное изменение состояний приложения (неудачный подход)

    //Код, выполняющийся при загрузке формы

    private void Form1_Load(object sender, System.EventArgs e) {

     textBox1.Visible = true;

     listBox1.Visible = false;

    }

    string m_someImportantInfo;

    //Пользователь щелкнул на кнопке, желая перейти к выполнению

    //следующего шага, предусмотренного в данном приложении. Скрыть

    //текстовое окно и отобразить окно списка в отведенном для этого месте

    private void button1_Click(object sender, System.EventArgs e) {

     m_someImportantInfo = textBox1.Text;

     textBox1.Visible = false;

     listBox1.Visible = true;

    }
     

    Подход 2: плановое, централизованное, явное управление состояниями (удачный подход)

    Явное управление состояниями — прямая противоположность предыдущему подходу. В этом случае любые переходы между состояния реализуются в рамках одной главной функции. В коде обработчика событий, ответственном за изменение некоторого аспекта состояния приложения, это обеспечивается вызовом единственной функции, которая также вызывается любым другим кодом, предназначенным для изменения состояния приложения в соответствии с логикой приложения. Пример реализации такого подхода представлен в листинге 5.3.

    Листинг 5.3. Явное изменение состояний приложения (удачный подход)

    string m_someImportantInfo;

    //Определить состояния, в которых может находиться приложение

    enum MyStates {

     step1, step2

    }

    //Главная функция, которая вызывается

    //всякий раз, когда возникает необходимость

    //в изменении состояния приложения

    void ChangeApplicationState(MyStates newState) {

     switch (newState) {

     case MyStates.step1:

      textBox1.Visible = true;

      listBox1.Visible = false;

      break;

     case MyStates.step2:

      m_someImportantInfo = textBox1.Text;

      textBox1.Visible = false;

      listBox1.Visible = true;

      break;

     }

    }

    //Пользователь щелкнул на кнопке, желая перейти к выполнению

    //следующего шага, предусмотренного в данном приложении. Скрыть

    //текстовое окно и отобразить окно списка в отведенном для этого месте

    private void button1_Click(object sender, System.EventArgs e) {

     //Вызвать главную функцию, осуществляющую изменение состояния

     ChangeApplicationState(MyStates.step2);

    }

    //Код, выполняющийся при загрузке формы

    private void Form1_Load(object sender, System.EventArgs e) {

     //Вызвать главную функцию, осуществляющую изменение состояния

     ChangeApplicationState(MyStates.step1);

    }

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

    Постойте-ка! Но ведь речь идет о мобильных приложениях. Разве размер их кода не должен быть меньше размера кода настольных приложений?

    Краткий ответ на этот вопрос звучит так: "Нет, не должен". Ваш код вовсе не должен быть меньше по размеру, чем аналогичный код для настольных компьютеров — он должен быть лучше! Среди разработчиков мобильных приложений бытует ошибочная точка зрения, согласно которой при написании кода лучше не разбивать его на отдельные функции, а стараться поместить в любую заданную функцию как можно больше кода и как можно чаще использовать встроенный код. Такой подход противоречит устоявшимся принципам проектирования. Назовем эту тенденцию "оптимизацией под микроскопом".

    Оптимизация более высокого порядка реализуются не на микроскопическом уровне, а на макроуровне. Проекты, в которых широко используется инкапсуляция, предоставляют гораздо больше возможностей для макрооптимизации. Имея единственный набор функций, предназначенных для управления приложением, вам будет легче работать со структурой приложения и тем самым оптимизировать ее. Чтобы обеспечить высокую производительность приложения, вам придется возвращаться к уже пройденным стадиям проекта, анализировать их и при необходимости изменять первоначальные предпосылки, которые, как выяснилось впоследствии, оказались неверными. В этом отношении централизованный подход к управлению состояниями приложения принесет вам гораздо больше пользы, чем распределенный специализированный подход.

    Существуют и такие ситуации, в которых действительно имеет смысл затратить усилия на оптимизацию отдельных алгоритмов и вынести код за пределы функций, сделав его встроенным. Это, например, может оказаться целесообразным в случае кода, выполняющего интенсивную многократную обработку крупных наборов данных в циклах. При необходимости можно систематически выявлять такие ситуации и принимать соответствующие меры. Однако для конечных автоматов, работающих на уровне приложения, которые вызываются при переходе приложения из одного режима выполнения операций или отображения пользовательского интерфейса в другой, такие ситуации не характерны; указанные изменения обычно осуществляются с низкой частотой и не встречаются внутри плотных циклов.

    Наконец, гораздо проще взять инкапсулированный код и вставить его в вызывающую функцию, чем пытаться преобразовать произвольный фрагмент кода к инкапсулированному виду, чтобы повысить эффективность работы с ним. Это вам подтвердит каждый, кому хотя бы раз приходилось заниматься упорядочением кода типа "спагетти", написанного другими разработчиками, приводя его к более удобной в работе инкапсулированной форме!

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

    Отдаете ли вы себе в этом отчет или нет, но значительная доля кода вашего приложения всегда будет связана с управлением его состояниями. Как бы то ни было, вам придется иметь дело с задачами подобного рода, и только вам решать, выбрать ли явный подход к управлению состояниями приложения или же остановиться на модели неявного управления. Явное управление состояниями имеет ряд существенных преимуществ, не самым последним из которых является то, что такой подход заставляет вас более тщательно продумывать схемы функционирования вашего приложения.

    На самых ранних стадиях разработки приложений и построения их прототипов формализация этих процессов, как правило, затруднена. Это ни в коем случае не должно отбивать у вас охоту к использованию конечных автоматов. Наоборот, конечные автоматы пригодятся вам на этих стадиях не меньше, чем на более поздних. Из предыдущего рассмотрения вы уже должны были понять, что для реализации конечного автомата требуется совсем небольшой объем дополнительного кода. При этом добавление новых состояний, а также удаление и переименование существующих состояний в соответствии с изменением определений по мере разработки ваших идей не составляет никакого труда. По моему мнению, при проектировании пользовательских интерфейсов мобильных приложений конечных автоматов, позволяющие централизованным образом экспериментировать с логикой размещения и масштабирования элементов управления, оказываются особенно полезными. Даже если разработка ведется в специализированном стиле, подход, основанный на использовании конечных автоматов, поможет вам сэкономить время и обеспечит необходимую гибкость в процессе уточнения ваших идей.

    Сколько конечных автоматов должно быть в приложении?

    Надеюсь, к этому моменту вы уже успели убедиться в целесообразности использования конечных автоматов в качестве центральной составляющей проекта мобильного приложения. Как и в случае любого другого аспекта проектирования, существуют некоторые общие принципы создания конечных автоматов, а также важные детали реализации. Конструирование подходящих разновидностей конечных автоматов является своего рода искусством, овладение которым приходит с приобретением опыта. Конечный автомат должен служить "абстрактным представлением одной или нескольких переменных, принимающих набор конечных значений, каждое из которых отображается в полезное дискретное состояние". Эти переменные должны некоторым образом коррелировать с различными состояниями, в которых может пребывать приложение или класс. Как и в случае приведенного выше примера кода, полезно явно определить переменную типа перечисления, значения которой, получающие определенные имена, задают действительное множество состояний, которыми вы хотите управлять посредством того или иного конечного автомата.

    Иногда удобно использовать несколько конечных автоматов в одном приложении. Конечный автомат, который управляет кэшированными данными, извлеченными из базы данных, может использоваться наряду с независимым конечным автоматом, управляющим такими графическими объектами, как перья и кисти, кэшируемыми для их использования в обычных задачах рисования. Третий конечный автомат может управлять выполнением задач фоновыми потоками. И хотя все перечисленные конечные автоматы можно было бы объединить в один "главный конечный автомат", такой комбинированный конечный автомат представлял бы просто формальное объединение всех возможных случаев изменения состояния приложения и не дал бы ничего нового, поскольку состояния, которыми управляют отдельные конечные автоматы, слабо или вообще не коррелируют друг с другом. Точно так же, возможна крайность другого рода, когда вместо создания одного конечного автомата, выполняющего всю работу, предпочитают иметь десятки небольших конечных автоматов, каждый из которых управляет отдельной переменной приложения. Роль ключа к нахождению правильного решения играет корреляция. Если можно выделить набор переменных, объектов или ресурсов, которые тем или иным образом коррелируют между собой, то управление этим набором удобно и целесообразно осуществлять при помощи одного конечного автомата.

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

    Конечный автомат для пользовательского интерфейса

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

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

    На рис. 5.2 представлены состояния пользовательского интерфейса нашей обучающей словарной игры для Pocket PC. Верхняя часть экрана отведена игровому полю. Персонаж перемещается по игровому полю, собирая объекты, если пользователь правильно отвечает на вопросы. На схеме представлены четыре состояния приложения, а стрелками указаны возможные переходы между состояниями пользовательского интерфейса (ПИ) 

    Рис. 5.2. Различные состояния пользовательского интерфейса в игре с множественным выбором


    Как видно из приведенной схемы, для каждого состояния имеется определенная информация, которую необходимо отобразить для пользователя, и определенная входная информация, представляющая ответы пользователя, возможность ввода которой должна обеспечиваться. В случае настольных компьютеров, размеры экранов которых достаточно велики, это не чревато какими-либо трудностями, однако в силу ограниченности экранного пространства мобильных устройств оно должно использоваться как можно более эффективно. Для вывода вопросов и ответов служат текстовые поля; кроме того, непосредственно поверх растрового изображения игрового поля выводится дополнительный текст. Пользователям предоставляются различные кнопки, с помощью которых они могут осуществлять свой выбор и переводить приложение в другое состояние. В данном примере потребовались четыре различных состояния пользовательского интерфейса.

    В дополнение к этому конечные автоматы оказываются весьма полезными во время экспериментирования с пользовательским интерфейсом и его окончательной доработки. Мы смогли очень быстро протестировать всевозможные позиционные и размерные параметры благодаря той легкости, с какой можно было изменять код компоновки элементов управления при помощи конечного автомата. Так, первоначально область вывода текста располагалась над игровым полем, но тестирование показало, что для повышения удобства пользования приложением эту область лучше разместить несколько ниже, в связи с чем она смещалась и тестировалась в самых разных конфигурациях. Кроме того, были проведены эксперименты с изменением размера текстового поля, используемого для отображения информации общего характера, представляющей интерес для пользователей, в различных состояниях приложения, в результате чего была найдена оптимальная конфигурация, обеспечивающая необходимый баланс эксплуатационных и эстетических характеристик. Хотя для выполнения описанной реорганизации пользовательского интерфейса применение конечного автомата вовсе не являлось обязательным, это намного упростило работу. Расположение всего кода, ответственного за параметры видимости, размеров и размещения, в одном месте позволяет значительно сократить сроки завершения этой части процесса итеративного проектирования, и сделать ее более понятной по сравнению с вариантом, в котором упомянутый код располагается вразброс в различных местах кода, реализующего логику приложения.

    Конечный автомат для модели памяти

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

    Данные, хранящиеся в памяти, часто представляют собой некий срез более крупного набора данных. В случае баз данных такие срезы, называемые также "представлениями" ("view"), отражают полный или частичный результат выполнения запроса к базе данных. Поскольку пользователю почти никогда не приходится работать одновременно со всеми данными, эффективность обработки данных можно значительно повысить, удерживая в памяти только те из них, в которых пользователь нуждается в текущий момент. Пользователи хотели бы работать с ощущением, что им доступен весь набор данных, но удовлетворение этого требования вовсе не означает, что все данные обязательно должны загружаться в память одновременно.

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

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

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

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

    Какой формат хранения данных в памяти является наиболее подходящим? При небольших объемах данных целесообразно выбирать тот формат, который более всего удобен с точки зрения программирования. С увеличением объема данных, например, при большом количестве строк, извлекаемых из базы данных, обязательным условием становится использование формата данных, оптимизированного в отношении расхода памяти и применяемых вычислительных методов. Кроме того, предусматриваемая модель должна обеспечивать сохранение в памяти недавно использованных данных и очистку памяти от всех остальных данных. 

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

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

    ■ Состав словаря. Используемый для игры словарь может содержать тысячи и даже десятки тысяч слов, каждое из которых должно снабжаться соответствующими определениями и примерами их использования. Мы могли бы попытаться загрузить в память одновременно все эти слова, но это было крайне неэкономно. Что нам необходимо — так это создать у пользователя впечатление, будто в памяти находятся сразу все слова, а на самом деле осуществлять хорошо продуманную подкачку слов в соответствие с развитием игры. Программное обеспечение должно справиться с этой задачей за счет использования гибкого конечного автомата и набора вспомогательных алгоритмов, поддерживающих создание нужной нам иллюзии. Наши алгоритмы управления памятью должны обеспечивать загрузку фиксированного числа случайных слов в память, возможность их использования в течение некоторого времени и последующее освобождение памяти для загрузки новых слов. Кроме того, желательно, чтобы проект был масштабируемым и позволял указывать количество слов, подлежащих загрузке в память, чтобы тем самым регулировать этот параметр по мере того, как мы лучше изучим потребности пользователей и возможности целевого мобильного устройства в отношении хранения этих данных в памяти. Поскольку количество слов, которые мы собираемся одновременно удерживать в памяти вместе с относящимися к ним определениями и другой родственной информацией, может исчисляться сотнями, то имеет смысл подумать о выборе наиболее подходящего способа хранения этих данных в памяти и доступа к ним. 

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

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

    Конечный автомат для фоновых задач

    В ваших мобильных приложениях обязательно будут встречаться участки кода, которые либо долго выполняются, либо требуют сетевого доступа; в разряд "длительно выполняющихся" попадают все операции, выполнение которых может занимать более 0,5 секунды. Некоторые возможные примеры таких операций приводятся ниже: 

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

    ■ Загрузка или сохранение больших объемов данных. Загрузка нескольких сотен порций информации из XML-файла или базы данных может занимать довольно большое время. 

    ■ Сетевые операции. Почти все операции, выполняемые в сети, подвержены риску задержек и других проблем связи.

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

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

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

    Рис. 5.3. Конечный автомат, предназначенный для управления выполнением фонового алгоритма нахождения простых чисел


    Стиль программирования: использование операторов goto и полных путей доступа в пространствах имен

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

    Использование операторов goto

    Вопрос о том, следует ли считать использование операторов goto в программах допустимым или же его необходимо рассматривать как признак плохого стиля программирования, является в определенной мере дискуссионным. Применимость операторов goto выступает предметом споров постольку, поскольку осуществляемая с их помощью передача управления противоречит принципам, лежащим в основе использования операторов условного перехода (if/then) и операторов циклов.

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

    1) Им соответствует передача управления только в прямом направлении.

    2) Они способствуют повышению удобочитаемости интересующих нас участков кода. Применение операторов goto может считаться непозволительным лишь в том случае, если это затрудняет понимание логики передачи управления в выполняющейся программе. Примером неудачного использования операторов goto может служить передача управления в различных направлениях, поскольку результатом этого является создание сложной системы замысловатых переходов внутри функций и трудности в отслеживании их логики; несомненно, любой из нас возражал бы против подобного использования этого оператора. 

    Использование полных путей доступа в пространствах имен

    Возможны два способа указания типов переменных в программах:

    1) В теле программы можно указывать полный путь доступа. Например, оператор System.Threading.Thread newThread; объявляет переменную newThread типа System.Threading.Thread. Достоинством таких подробных объявлений является простота их применения.

    2) В начале файла с текстом программы можно задавать использование определенного пространства имен, применяя для этого ключевое слово using языка C# или ключевое слово Imports языка Visual Basic .NET. Например, если в начале файла с программой на языке C# содержится оператор using SystemThreading;, то переменная System.Threading.Thread может быть объявлена просто как Thread newThread; без указания ее полного имени.

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

    На заметку! Прежде чем вы сможете использовать те или иные классы и типы в своей программе, вы должны указать, к каким сборкам (assembly), они относятся. Очень важно, чтобы вы понимали, что ключевые слова using и Imports являются всего лишь удобным синтаксическим средством и не отменяют необходимости включения ссылки на сборку. Чтобы компилятору стали известны типы, содержащиеся в сборке, вы должны явно включить соответствующую ссылку в свою программу. В Visual Studio .NET список сборок можно просмотреть с помощью элемента управления TreeView в окне проводника решений Solution Explorer. Как правило, предварительно сконфигурированные часто используемые ссылки автоматически включаются во вновь создаваемые проекты.

    Листинг 5.4. Код программы нахождения простых чисел, предназначенный для выполнения фоновым потоком 

    using System;


    public class FindNextPrimeNumber
    {

     //Определить возможные состояния


     public enum ProcessingState {

      notYetStarted,

      waitingToStartAsync,

      lookingForPrime,

      foundPrime,

      requestAbort,

      aborted

     }

     int m_startTickCount;

     int m_endTickCount;

     long m_startPoint;

     long m NextHighestPrime;

     ProcessingState m_processingState;

     //---------------------------

     //Простейший конечный автомат

     //---------------------------

     public void setProcessingState(ProcessingState nextState) {

      //-----------------------------------------------------

      // Простейший защитный код, гарантирующий невозможность

      // перехода в другое состояние в случае успешного

      // завершения задачи или успешной отмены ее выполнения

      //-----------------------------------------------------

      if ((m_processingState == ProcessingState.aborted) || (m_processingState == ProcessingState.foundPrime)) {

       return;

      }

      //Разрешить изменение состояния

      lock(this) {

       m_processingState = nextState;

      }

     }

     public ProcessingState getProcessingState() {

      ProcessingState currentState; //Безопасное выполнение потока

      lock(this) {

       currentState = m_processingState;

      }

      return currentState;

     }

     public int getTickCountDelta() {

      if (getProcessingState() == ProcessingState.lookingForPrime) {

       throw new Exception("Продолжается поиск простого числа! Окончательное время еще не вычислено");

      }

      return m_endTickCount - m_startTickCount;

     }

     //-------------------------

     // Возвращает простое число

     //-------------------------

     public long getPrime() {

      if (getProcessingState() != ProcessingState.foundPrime) {

       throw new Exception("простое число еще не найдено!");

      }

      return m_NextHighestPrime;

     }

     //Конструктор класса

     public FindNextPrimeNumber(long startPoint) {

      setProcessingState(ProcessingState.notYetStarted);

      m_startPoint = startPoint;

     }

     //------------------------------------------------------------

     // Создает новый рабочий поток, который будет вызывать функцию

     // "findNextHighestPrime()"

     //------------------------------------------------------------

     public void findNextHighestPrime_Async() {

      System.Threading.ThreadStart threadStart;

      threadStart =  new System.Threading.ThreadStart(findNextHighestPrime);

      System.Threading.Thread newThread;

      newThread = new System.Threading.Thread(threadStart);

      //Состояние должно отвечать, что поиск продолжается

      setProcessingState(ProcessingState.waitingToStartAsync);

      newThread.Start();

      //-----------------------------------------------------------------

      // Основной рабочий поток. Этот поток запускает поиск очередного

      // простого числа и выполняется до тех пор, пока не произойдет

      // одно из следующих двух событий:

      // (а) найдено очередное простое число

      // (b) от внешнего (по отношению к данному) потока поступила команда

      // прекратить выполнение

      //------------------------------------------------------------------

     public void findNextHighestPrime() {

      //Если поступила команда прекратить выполнение, то поиск

      //даже не должен начинаться

      if (getProcessingState() == ProcessingState.requestAbort) {

       goto finished_looking;

      }

      //Состояние должно отвечать, что поиск продолжается

      setProcessingState(ProcessingState.lookingForPrime);

      m_startTickCount = System.Environment.TickCount;

      long currentItem;

      //Проверить, является ли число нечетным

      if ((m_startPoint & 1) == 1) {

       //Число является нечетным, начать поиск со следующего нечетного числа

       currentItem = m_startPoint + 2;

      } else {

       //Число является четным, начать поиск со следующего нечетного числа

       currentItem = m_startPoint + 1;

      }

      //Приступить к поиску простого числа

      while(getProcessingState() == ProcessingState.lookingForPrime) {

       //B случае нахождения простого числа возвратить его

       if (isItemPrime(currentItem) == true) {

        m_NextHighestPrime = currentItem; //Обновить состояние

        setProcessingState(ProcessingState.foundPrime);

       }

       currentItem = currentItem + 2;

      }

    finished_looking:

      //Выход. К этому моменту либо от другого потока поступила

      //команда прекратить поиск, либо было найдено и записано

      //следующее наибольшее простое число

      //Зафиксировать время

      m_endTickCount = System.Environment.TickCount;

      //Если поступил запрос прекратить выполнение,

      //сообщить, что выполнение процесса прекращено

      if (getProcessingState() == ProcessingState.requestAbort) {

       setProcessingState(ProcessingState.aborted);

      }

     }

     //Конец функции

     //Вспомогательная функция, которая проверяет, является

     //ли число простым

     private bool isItemPrime(long potentialPrime) {

      //Если число — четное, значит, оно не является простым

      if ((potentialPrime & 1) == 0) {

       return false;

      }

      //Продолжать поиск до тех пор, пока не будет превышено

      //значение квадратного корня из числа

      long end_point_of_search;

      end_point_of_search = (long) System.Math.Sqrt(potentialPrime) + 1;

      long current_test_item = 3;

      while (current_test_item <= end_point_of_search ) {

      //----------------------------------------------------------

      // Проверить, не поступила ли команда прекратить выполнение!

      //----------------------------------------------------------

      if (getProcessingState() != ProcessingState.lookingForPrime) {

       return false;

      }

      //Если число делится без остатка,

      //значит, оно не является простым

      if (potentialPrime % current_test_item == 0) {

       return false;

      }

      //Увеличить число на два

      current_test_item = current_test_item + 2;

     }

     //Число является простым return true;

     }

    } //конец класса

    В листинге 5.5 содержится код, который может быть помещен в форму для тестирования приведенного выше алгоритма фоновой обработки.

    Листинг 5.5. Тестовая программа, которая вызывает на выполнение приведенный выше код фонового потока, осуществляющего поиск простого числа

    //-----------------------------------------------------------

    // Код, обрабатывающий событие щелчка на кнопке Button1 формы

    //

    // Вызвать из этого потока функцию поиска простого числа!

    // (Это приведет к блокированию потока)

    //-----------------------------------------------------------

    private void button1_Click(object sender, System.EventArgs e) {

     long testItem;

     testItem = System.Convert.ToInt64("123456789012345");

     FindNextPrimeNumber nextPrimeFinder;

     nextPrimeFinder = new FindNextPrimeNumber(testItem);

     nextPrimeFinder.findNextHighestPrime();

     long nextHighestPrime;

     nextHighestPrime = nextPrimeFinder.getPrime();

     System.Windows.Forms.MessageBox.Show(System.Convert.ToString(nextHighestPrime));

     //Сколько времени заняли вычисления?

     int calculation_time;

     calculation_time = nextPrimeFinder.getTickCountDelta();

     System.Windows.Forms.MessageBox.Show(System.Convert.ToString(calculation_time) + " мс");

    }

    //------------------------------------------------------------------------

    // Код, обрабатывающий событие щелчка на кнопке Button2 формы

    //

    // Вызвать функцию поиска простого числа из другого потока!

    // (Данный поток блокироваться не будет)

    // Для отслеживания состояния выполнения задачи используем конечный автомат

    //-------------------------------------------------------------------------

    private void button2_Click(object sender, System.EventArgs e) {

     long testItem;

     testItem = System.Convert.ToInt64("123456789012345");

     FindNextPrimeNumber nextPrimeFinder;

     nextPrimeFinder = new FindNextPrimeNumber(testItem);

     //------------------------------------

     // Выполнить обработку в другом потоке

     //------------------------------------

     nextPrimeFinder.findNextHighestPrime_Async();

     //Войти в цикл и ожидать до тех пор, пока не будет найдено

     //простое число или выполнение не будет прекращено

     while ((nextPrimeFinder.getProcessingState() != FindNextPrimeNumber.ProcessingState.foundPrime) &&

      (nextPrimeFinder.getProcessingState() != FindNextPrimeNumber.ProcessingState.aborted)) {

      //ТОЛЬКО В ТЕСТОВОМ КОДЕ:

      //Отобразить окно сообщений и предоставить пользователю

      //возможность убрать его с экрана.

      //Это позволяет организовать паузу!

      System.Windows.Forms.MessageBox.Show("Поиск продолжается... Щелкните на кнопке OK");

      //Мы могли бы прекратить поиск путем следующего вызова функции:

      //nextPrimeFinder.setProcessingState(

      // FindNextPrimeNumber.ProcessingState.requestAbort);

     }

     //Осуществить корректный выход в случае прекращения поиска

     if (nextPrimeFinder.getProcessingState() == FindNextPrimeNumber.ProcessingState.aborted) {

      System.Windows.Forms.MessageBox.Show("Поиск прекращен!");

      return;

     }

     long nextHighestPrime;

     nextHighestPrime = nextPrimeFinder.getPrime();

     System.Windows.Forms.MessageBox.Show(System.Convert.ToString(nextHighestPrime));

     //Сколько времени заняли вычисления?

     int calculation_time;

     calculation_time = nextPrimeFinder.getTickCountDelta();

     System.Windows.Forms.MessageBox.Show(System.Convert.ToString(calculation_time) + " мс");

    }

    Для выполнения примера с использованием указанного в листинге начального числа (123456789012345) на моем эмуляторе Pocket РС требовалось от 10 до 20 секунд. Исследуйте зависимость времени вычислений от количества цифр в начальном числе. (Как правило, с увеличением количества цифр время вычислений увеличивается.)

    НА ЗАМЕТКУ

    Чтобы не усложнять пример, количество правил, используемых в функции управления переходом, было сознательно выбрано небольшим. В случае фактической реализации количество правил целесообразно увеличить, например, разрешая только допустимые переходы между состояниями, и вырабатывая исключения в непредвиденных ситуациях. Наличие жестких правил, регламентирующих возможность изменения состояния, оказывается очень полезным при поиске трудно обнаруживаемых ошибок, обусловленных выполнением кода сразу несколькими потоками. В подобных ситуациях процесс выполнения усложняется, так что введение надежных правил, осуществление проверок с помощью операторов ASSERT и генерация исключений значительно облегчат вам поиск тонких ошибок, избавив от возможной многочасовой работы по их устранению.

    Использование конечных автоматов в играх

    Конечные автоматы весьма удобно использовать в играх, в которых различные персонажи, перемещающиеся в области игры, могут действовать в различных режимах. Например, каждый персонаж может находиться в различных состояниях, соответствующих отдельным направлениям или характеру движения (персонаж обращен лицом влево или вправо, бежит вперед или назад, взбирается по лестнице, падает и тому подобное). Использование конечных автоматов с четко определенными правилами для каждого персонажа, определяющими возможные варианты его поведения в различных состояниях и допустимые переходы из одного состояния в другое, позволяет создавать сложные варианты поведения как персонажей, управляемых компьютером, так и персонажей, управляемых пользователем.

    Резюме 

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

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







     


    Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх