В общем случае приложение пользовательского интерфейса всегда ожидает события. Событие может быть действием пользователя (касание, встряхивание iPhone, ввод буквы на виртуальной клавиатуре) или другим процессом (сетевой пакет становится доступным, батарея разряжается) или событием времени (истекает таймер). Всякий раз, когда происходит событие («если это»), вы обращаетесь к текущему состоянию вашего приложения («... и это, а не другое»), а затем делаете что-то («делайте x и y»), что, скорее всего, меняется состояние приложения («установить состояние z»). Это то, что вы описали в своем вопросе. И это общая закономерность.
Не существует единого системного подхода, чтобы сделать его правильным, но когда вы спрашиваете о предложениях подходов, вот несколько предложений:
СОВЕТ 1: Используйте как можно меньше и меньше реальных структур данных и переменных для представления внутреннего состояния, насколько это возможно, избегая дублирования состояния всеми средствами (пока вы не столкнетесь с проблемами производительности). Это делает «do x and y и set state z» короче, потому что состояние устанавливается неявно. Тривиальный пример: вместо того, чтобы иметь (примеры в C ++)
if (namelen < 20) { name.append(c); namelen++; }
использование
if (name.size() < 20) { name.append(c); }
Во втором примере правильно избегается реплицированная переменная состояния namelen, что делает часть действия короче.
СОВЕТ 2: Всякий раз, когда составное условие (X и Y или Z) появляется в вашей программе много раз, абстрагируйте его в процедуру, поэтому вместо
if ((x && y) || z) { ... }
запись
bool my_condition() { return (x && y) || z; }
if (my_condition()) { ... }
СОВЕТ 3: Если ваш пользовательский интерфейс имеет небольшое количество четко определенных состояний, и эти состояния влияют на обработку событий, вы можете представлять состояния как единичные экземпляры классов, которые наследуются от интерфейса для обработки этих событий. Например:
class UIState {
public:
virtual void HandleShake() = 0;
}
class MainScreen : public UIState {
public:
void HandleShake() { ... }
}
class HelpScreen : public UIState {
public:
void HandleShake() { ... }
}
Создает экземпляр одного экземпляра каждого производного класса и имеет указатель, указывающий на объект текущего состояния:
UIState *current;
UIState *mainscreen = new MainScreen();
UIState *helpscreen = new HelpScreen();
current = mainscreen;
Чтобы справиться со встряхиванием, позвоните:
current->HandleShake();
Чтобы изменить состояние пользовательского интерфейса позже:
current = helpscreen;
Таким образом, вы можете собирать связанные с состоянием процедуры в классы, инкапсулировать и абстрагировать их. Конечно, вы можете добавлять в эти классы, зависящие от состояния (singleton), все виды интересных вещей.
СОВЕТ 4: В общем случае, если у вас есть N переменных логического состояния и T различных событий, которые могут быть запущены, в матрице T * 2 ** N записей всех возможных событий во всех возможных условиях. Это требует вашего архитектурного представления и опыта предметной области, чтобы правильно идентифицировать те измерения и области в матрице, которые наиболее логично и естественно для инкапсуляции в объекты, и как. И это то, что разработка программного обеспечения. Но если вы попытаетесь сделать свой проект без надлежащей инкапсуляции и абстракции, вы не сможете масштабировать его далеко.