воскресенье, 22 апреля 2012 г.

6. Проектируем БД

На прошлой неделе мы завершили разработку прототипа нашей игры. Большинство авторов статей по программированию, обычно, этими и ограничиваются, полагая, что все самое интересное рассказано. На мой взгляд, интересное только начинается...


6. Проектируем БД


Давайте попробуем привести наш набросок игры к более менее товарному виду (продать его разумеется не удастся, ввиду наличия на Android Market несметного количества совершенно аналогичных бесплатных приложений, но мы ведь говорим о разработке?).

Вновь сталкиваясь с "ужасом чистого листа", вспоминаем, что разработку любого мало мальски сложного приложения, нужно начинать с проектирования его данных. Нужна ли нашему приложению БД? На мой взгляд, несомненно (нужно же нам где-то сохранять текущее состояние при выгрузке приложения, да и сами головоломки необходимо где-то хранить).

Платформа Android заботливо решает за нас непростой вопрос выбора СУБД для хранения данных, предоставляя в наше пользование SQLite. Хотя эту СУБД вряд-ли стоит использовать для организации к БД многопользовательского доступа, для разработки встраиваемых БД она подходит идеально (за исключением легких неприятностей с сортировкой кирилицы, единственным известным мне недостатком SQLite явлется несколько необычное понимание того, чем являются транзакции, что делает практически невозможным изменение данных в БД SQLite несколькими пользователями одновременно).

Разработку БД я рекомендую начинать с ER-диаграммы. Ее значение настолько трудно переоценить, что даже если бы ее пришлось рисовать в Paint-е, это стоило бы сделать, чтобы иметь перед глазами наглядую схему того как организованы данные, когда придется составлять SQL-запросы. Разумеется, гораздо лучше использовать специализированное CASE-средство.






Эта диаграмма обескураживает o O
И вызывает законные вопросы:

  1. Кто все эти таблицы?
  2. И неужели все это действительно нужно???
Допуская вероятность, что я действительно мог в этом отношении несколько переусердствовать, в оставшейся  части статьи я постараюсь описать все эти таблицы.

Locale


Хотя Anroid предоставляет великолепные средства для локализации строковых ресурсов, в нашем проекте мы этими средствами воспользоваться не сможем, поскольку часть строковых ресурсов будет связана с головоломками сохраненными в БД и возможно создаваемыми самим пользователем (то есть не будет доступна на момент сборки приложения). Описания доступных локалей мы будем хранить в таблице Locale. В поле name будем хранить наименование локали в Java (для возможности автоматического определения текущей локали), поле is_default будет задавать локаль по умолчанию, а string_id будет ссылаться на  наименование локали, используемое в нашем приложении.

String


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

String_Value


Конкретные значения строковых ресурсов с учетом их локали. В locale_id хранится идентификатор локали в string_id идентификатор строкового ресурса, а в value, собственно строковое значение.

Puzzle


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

End_Position


Мы вполне могли бы хранить финальную позицию (ту при достижении которой головоломка считается решенной) в puzzle.end_position_id, если бы такая позиция была одна :) Вообще говоря может быть несколько равноценных позиций, при достижении любой из которых, головоломка считается решенной. 
Поскольку мы имеем дело с соотношением 1:N, создаем таблицу в которой будем хранить идентификатор головоломки puzzle_id и идентификаторы ее конечных позиций position_id.

Param


Параметры головоломки (их как минимум 2: размер игрового поля по вертикали и горизонтали). Тип параметра будет определяться значением param_type_id, а значение будет храниться в value. В puzzle_id, разумеется, будет сохраняться идентификатор головоломки, к которой относятся эти параметры. 

Param_Type


Снова таблица, которая вряд-ли будет участвовать в наших SQL-запросах. В ней будут храниться типы возможных параметров (все два) и без нее param.param_type_id будет смотреть в пустоту :(

Tag


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

Item


Таблица item хранит составляющие наших плашек (элементарные квадратики). В tag_id хранится идентификатор плашки, в x и y относительные координаты item-а по горизонтали и вертикале. По крайней мере один item в каждом tag должен будет иметь координаты <0, 0>.

Tag_Position


Третья наша главная таблица, в которой будут сохраняться координаты tag-ов (если быть точнее, их item-ов с координатами <0, 0>) в некоторой позиции. Таблица содержит идентификатры tag-а и позиции (tag_id и position_id), а также координаты x и y (начиная с 1). Таким образом, координаты item-а будут вычисляться как <item.x + tag_position.x, item.y + tag_position.y>.
Следует заметить, что позиция совершенно не обязана описывать координаты всех tag-ов головоломки, что будет нами использоваться при описании финальных позиций.

Position


Еще одна неиспользуемая таблица, чтобы position_id было на что ссылаться.

Profile


Таблица, которая, скорее всего, всегда будет содержать одну и только одну запись (в которой is_default будет равно 1). Но должны же мы где-то сохранять текущие настройки, на время выгрузки приложения? 
В puzzle_id мы будем хранить идентификатор текущей головоломки, а в locale_id - текущей локали. Ну а если нам когда нибудь придет в голову добавить поддержку пользовательских профилей, мы будем знать, куда добавить записи (тут-то и login пригодится).

Session


Таблица session также будет хранить текущие настройки. Помимо идентификатора профиля profile_id, в ней будет храниться идентификатор головоломки puzzle_id и идентификатор текущей позиции position_id. Эта таблица будет хранить несколько (возможно 0) записей. Текущая сессия будет помечена флагом is_current, а закрытые сессии (для таблицы рекордов) будут помечены is_closed. Строго говоря, хватило бы и одного из этих атрибутов, но мне лень перерисовывать диаграмму :)

Stat


Эта таблица будет содержать значения параметров связанных с сессией. Пока я могу придумать только один такой параметр - количество выполненных ходов (напомню, что по времени мы нашего пользователя решили никак не ограничивать,  и это серьезное маркетинговое решение). Помимо идентификатора сессии session_id, в таблице определено поле stat_type_id (вдруг еще какие-то типы придумаем) и value, в котором будем хранить значение параметра. К слову сказать, тип поля value мы выбрали неправильно, но об этом мы узнаем только когда приступим к реализации таблицы рекордов. Пока-же мы пребываем в блаженном неведении (я ведь уже говорил, что мне лень все это перерисовывать???).

Stat_Type


Если есть типы, их надо где-то хранить. Я настаиваю на этом :)

Solution_Step


Ну и наконец последня наша таблица, в которой мы будем сохранять выполненные в рамках сессии ходы. Она сильно нам поможет, если мы захотим выполнять Undo/Redo после перезагрузки приложения. Помимо session_id, она будет содержать tag_id, приращения его координат в рамках хода dx и dy (возможно отрицательные, но одно из них должно будет равняться 0) и порядковый номер хода ord_num, уникальный в пределах сессии (наименование возможно не сильно удачно, но фантазия в этом месте кончилась).

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

Комментариев нет:

Отправить комментарий