Как настроить смартфоны и ПК. Информационный портал

Разделение кода. Модель MVC

Фреймворк Bootstrap: быстрая адаптивная вёрстка

Пошаговый видеокурс по основам адаптивной верстки в фреймворке Bootstrap.

Научитесь верстать просто, быстро и качественно, используя мощный и практичный инструмент.

Верстайте на заказ и получайте деньги.

Бесплатный курс "Сайт на WordPress"

Хотите освоить CMS WordPress?

Получите уроки по дизайну и верстке сайта на WordPress.

Научитесь работать с темами и нарезать макет.

Бесплатный видеокурс по рисованию дизайна сайта, его верстке и установке на CMS WordPress!

*Наведите курсор мыши для приостановки прокрутки.

Назад Вперед

Удобный подход к веб-разработке: Модель MVC

Что такое MVC? Если отвечать кратко, то это подход к разработке, позволяющий добиться более структурированного кода и приложения в целом.

Расшифровывается MVC как "Модель-Вид-Контроллер" (Model-View-Controller). Давайте поговорим об этом подробнее.

На сегодняшний день можно выделить два наиболее типичных способа создания веб-приложений (сайтов).

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

Скажем, в начале файла производится запрос к базе данных для получения из нее какой-либо информации. Здесь мы имеем дело с языком SQL - специальным языком запросов, предназначенным для взаимодействия с базой данных.

После этого, как правило, начинается html-разметка страницы (куда же без нее?). Причем внутри html-разметки в нужных местах производятся вставки PHP-кода, которые производят управление сайтом, являются его логикой. Итого мы имеем в одном файле: SQL, (X)HTML и PHP. Это уже - сборная солянка. Не забудьте добавить сюда еще CSS и немного Javascript для полноты картины, и, в итоге мы получим такую кашу, что в этом файле сам черт ногу сломит.

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

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

Второй способ связан как раз-таки с применением схемы "Модель-Вид-Контроллер" .

В чем суть данного подхода, и как его использование может помочь вам в работе?

Основная идея данного подхода заключается в необходимости разделения однородных элементов по разным файлам . Если говорить очень упрощенно: один файл - один язык. Но это очень грубый пример. Такое встречается крайне редко.

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

Здесь мы начинаем с вами подходить к более подробному рассмотрению модели MVC.

Данная модель подразумевает разделение всех файлов, задействованных при разработке сайта на три группы:

1. Файлы группы "модель"
2. Файлы группы "контроллер"
3. Файлы группы "вид"

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

Итак, давайте посмотрим на сравнительную схему модели MVC и "классического" способа разработки .


В левой части вы видите как раз то, о чем мы говорили выше. Вверху страницы - SQL-запросы к базе. Затем разметка плюс вставки PHP.

Справа же приведена простейшая схема модели MVC. В рамках данной схемы в модели происходят операции, связанные с взаимодействием с базой данных : извлечение данных, их модификация и удаление, подсчет количества записей в определенных таблицах и т.п.

В контроллере находится логика приложения , т.е. то, что определяет его функционал.

Вид же предназначен для показа конечному пользователю .

Двунаправленные стрелки на схеме показывают то, что в парах "Модель - Контроллер" и "Контроллер - Вид" существует взаимосвязь. Рассмотрим эту взаимосвязь подробнее на примере следующей схемы.


На этой схеме у нас добавилось два новых элемента: браузер пользователя и база данных. Рассмотрим в общих чертах весь цикл: от обращения браузера к определенному url-адресу до момента отображения страницы для пользователя:

1. Пользователь вводит адрес, и браузер обращается к контроллеру.

2. Контроллер обращается к модели.

3. Модель обращается к базе данных (к примеру, для получения необходимой для вывода информации)

4. Информация из базы попадает обратно в модель.

5. Из модели информация передается в контроллер.

6. Контроллер передает эту информацию в вид.

7. Вид выводится в браузер с помощью контроллера.

Такова общая схема работы данной модели. Как вы можете видеть, некоторым особняком на данной схеме стоят браузер и база данных. Действительно, браузер может обращаться только к контроллеру, так как контроллер является частью url-адреса . Посетитель не может обратиться к чему бы то ни было, кроме контроллера. Это важно понимать. Человек не может через адресную строку обращаться к видам или моделям. Он взаимодействует только с контроллером.

В связи с этим можно говорить о контроллере как о своеобразном "распределительном центре". Смотрите сами: контроллер обрабатывает запросы пользователя, контроллер обращается к модели, контроллер же является посредником для вывода вида в браузер.

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

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

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

"Крайних" мы рассмотрели, а в центре схемы так и осталась наша троица, где происходят взаимодействия "Модель - Контроллер" и "Контроллер - Вид".

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

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

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

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

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

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

Ниже вы можете видеть часть файла, относящегося к группе "виды":


А вот кусок кода из модели:


Так может выглядеть контроллер:


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

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

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

Очевидны преимущества применения модели MVC в рамках фреймворка , например, того же CodeIgniter.

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

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

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

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

Послесловие: Не забывайте о том, что любая структура, которая была создана для облегчения выполнения определенных задач, была создана именно для облегчения работы.

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

Дмитрий Науменко

P.S. Думаете, какой бы PHP-фреймворк освоить? Обратите внимание на CakePHP - он реализует рассмотренный выше паттерн MVC, и прямо сейчас вы можете получить небольшой вводный видеокурс, чтобы получить общее представление о возможностях этого фреймворка:

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


Паттерн Model-View-Controller (MVC) является крайне полезным при создании приложений со сложным графическим интерфейсом или поведением. Но и для более простых случаев он также подойдет. В этой заметке мы создадим игру сапер, спроектированную на основе этого паттерна. В качестве языка разработки выбран Python, однако особого значения в этом нет. Паттерны не зависят от конкретного языка программирования и вы без труда сможете перенести получившуюся реализацию на любую другую платформу.

Реклама

Коротко о паттерне MVC

Как следует из названия, паттерн MVC включает в себя 3 компонента: Модель, Представление и Контроллер. Каждый из компонентов выполняет свою роль и является взаимозаменяемым. Это значит, что компоненты связаны друг с другом лишь некими четкими интерфейсами, за которыми может лежать любая реализация. Такой подход позволяет подменять и комбинировать различные компоненты, обеспечивая необходимую логику работы или внешний вид приложения. Разберемся с теми функциями, которые выполняет каждый компонент.

Модель

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

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

Представление

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

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

Кроме того, следует учитывать, что в обязанности Представления входит лишь своевременное отображение состояния Модели. За обработку действий пользователя отвечает Контроллер, о которым мы сейчас и поговорим.

Контроллер

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

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

Реклама

Спецификации игры Сапер

Достаточно теории. Теперь перейдем к практике. Для демонстрации паттерна MVC мы напишем несложную игру: Сапер. Правила игры достаточно простые:

  1. Игровое поле представляет собой прямоугольную область, состоящую из клеток. В некоторых клетках случайным образом расположены мины, но игрок о них не знает;
  2. Игрок может щелкнуть по любой клетке игрового поля левой или правой кнопками мыши;
  3. Щелчок левой кнопки мыши приводит к тому, что клетка будет открыта. При этом, если в клетке находится мина, то игра завершается проигрышем. Если в соседних клетках, рядом с открытой, расположены мины, то на открытой клетке отобразится счетчик с числом мин вокруг. Если же мин вокруг открытой клетки нет, то каждая соседняя клетка будет открыта по тому же принципу. То есть клетки будут открываться до тех пор, пока либо не упрутся в границу игрового поля, либо не дойдут до уже открытых клеток, либо рядом с ними не окажется мина;
  4. Щелчок правой кнопки мыши позволяет делать пометки на клетках. Щелчок на закрытой клетке помечает ее флажком, который блокирует ее состояние и предотвращает случайное открытие. Щелчок на клетке, помеченной флажком, меняет ее пометку на вопросительный знак. В этом случае клетка уже не блокируется и может быть открыта левой кнопкой мыши. Щелчок на клетке с вопросительным знаком возвращает ей закрытое состояние без пометок;
  5. Победа определяется состоянием игры, при котором на игровом поле открыты все клетки, за исключением заминированных.

Пример того, что у нас получится приведен ниже:

UML-диаграммы игры Сапер

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

Диаграмма Состояний игровой клетки

Любая клетка на игровом поле может находиться в одном из 4 состояний:

  1. Клетка закрыта;
  2. Клетка открыта;
  3. Клетка помечена флажком;
  4. Клетка помечена вопросительным знаком.

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

Диаграмма Классов игры Сапер

Поскольку мы решили создавать наше приложение на основе паттерна MVC, то у нас будет три основных класса: MinesweeperModel , MinesweeperView и MinesweeperController , а также вспомогательный класс MinesweeperCell для хранения состояния клетки. Рассмотрим их диаграмму классов:

Организация архитектуры довольно проста. Здесь мы просто распределили задачи по каждому классу в соответствии с принципами паттерна MVC:

  1. В самом низу иерархии расположен класс игровой клетки MinesweeperCell . Он хранит позицию клетки, определяемую рядом row и столбцом column игрового поля; одно из состояний state , которые мы описали в предыдущем подразделе; информацию о наличии мины в клетке (mined) и счетчик мин в соседних клетках counter . Кроме того, у него есть два метода: nextMark() для циклического перехода по состояниям, связанным с пометками, появляющимися в результате щелчка правой кнопкой мыши, а также open() , который обрабатывает событие, связанное с щелчком левой кнопкой мыши;
  2. Чуть выше расположен класс Модели MinesweeperModel . Он является контейнером для игровых клеток MinesweeperCell . Его первый метод startGame() подготавливает игровое поле для начала игры. Метод isWin() делает проверку игрового поля на состояние выигрыша и возвращает истину, если игрок победил, иначе возвращается ложь. Для проверки проигрыша предназначен аналогичный метод isGameOver() . Методы openCell() и nextCellMark() всего лишь делегируют действия соответствующим клеткам на игровом поле, а метод getCell() возвращает запрашиваемую игровую клетку;
  3. Класс Представления MinesweeperView включает следующие методы: syncWithModel() - обеспечивает перерисовку Представления для отображения актуального состояния игрового поля в Модели; getGameSettings() - возвращает настройки игры, заданные пользователем; createBoard() - создает игровое поле на основе данных Модели; showWinMessage() и showGameOverMessage() соответственно отображают сообщения о победе и проигрыше;
  4. И наконец класс Контроллера MinesweeperController . В нем определено всего три метода на каждое возможное действие игрока: startNewGame() отвечает за нажатие на кнопке "Новая игра" в интерфейсе Представления; onLeftClick() и onRightClick() обрабатывают щелчки по игровым клеткам левой и правой кнопками мыши соответственно.

Реализация игры Сапер на Python

Пришло время заняться реализацией нашего проекта. В качестве языка разработки выберем Python. Тогда класс Представления будем писать на основе модуля tkinter .

Но начнем с Модели.

Модель MinsweeperModel

Реализация модели на языке Python выглядит следующим образом:

MIN_ROW_COUNT = 5 MAX_ROW_COUNT = 30 MIN_COLUMN_COUNT = 5 MAX_COLUMN_COUNT = 30 MIN_MINE_COUNT = 1 MAX_MINE_COUNT = 800 class MinesweeperCell: # Возможные состояния игровой клетки: # closed - закрыта # opened - открыта # flagged - помечена флажком # questioned - помечена вопросительным знаком def __init__(self, row, column): self.row = row self.column = column self.state = "closed" self.mined = False self.counter = 0 markSequence = [ "closed", "flagged", "questioned" ] def nextMark(self): if self.state in self.markSequence: stateIndex = self.markSequence.index(self.state) self.state = self.markSequence[ (stateIndex + 1) % len(self.markSequence) ] def open(self): if self.state != "flagged": self.state = "opened" class MinesweeperModel: def __init__(self): self.startGame() def startGame(self, rowCount = 15, columnCount = 15, mineCount = 15): if rowCount in range(MIN_ROW_COUNT, MAX_ROW_COUNT + 1): self.rowCount = rowCount if columnCount in range(MIN_COLUMN_COUNT, MAX_COLUMN_COUNT + 1): self.columnCount = columnCount if mineCount < self.rowCount * self.columnCount: if mineCount in range(MIN_MINE_COUNT, MAX_MINE_COUNT + 1): self.mineCount = mineCount else: self.mineCount = self.rowCount * self.columnCount - 1 self.firstStep = True self.gameOver = False self.cellsTable = for row in range(self.rowCount): cellsRow = for column in range(self.columnCount): cellsRow.append(MinesweeperCell(row, column)) self.cellsTable.append(cellsRow) def getCell(self, row, column): if row < 0 or column < 0 or self.rowCount <= row or self.columnCount <= column: return None return self.cellsTable[ row ][ column ] def isWin(self): for row in range(self.rowCount): for column in range(self.columnCount): cell = self.cellsTable[ row ][ column ] if not cell.mined and (cell.state != "opened" and cell.state != "flagged"): return False return True def isGameOver(self): return self.gameOver def openCell(self, row, column): cell = self.getCell(row, column) if not cell: return cell.open() if cell.mined: self.gameOver = True return if self.firstStep: self.firstStep = False self.generateMines() cell.counter = self.countMinesAroundCell(row, column) if cell.counter == 0: neighbours = self.getCellNeighbours(row, column) for n in neighbours: if n.state == "closed": self.openCell(n.row, n.column) def nextCellMark(self, row, column): cell = self.getCell(row, column) if cell: cell.nextMark() def generateMines(self): for i in range(self.mineCount): while True: row = random.randint(0, self.rowCount - 1) column = random.randint(0, self.columnCount - 1) cell = self.getCell(row, column) if not cell.state == "opened" and not cell.mined: cell.mined = True break def countMinesAroundCell(self, row, column): neighbours = self.getCellNeighbours(row, column) return sum(1 for n in neighbours if n.mined) def getCellNeighbours(self, row, column): neighbours = for r in range(row - 1, row + 2): neighbours.append(self.getCell(r, column - 1)) if r != row: neighbours.append(self.getCell(r, column)) neighbours.append(self.getCell(r, column + 1)) return filter(lambda n: n is not None, neighbours)

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

MIN_ROW_COUNT = 5 MAX_ROW_COUNT = 30 MIN_COLUMN_COUNT = 5 MAX_COLUMN_COUNT = 30 MIN_MINE_COUNT = 1 MAX_MINE_COUNT = 800

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

Затем мы определили класс игровой клетки MinesweeperCell . Она оказалась достаточно простой. В конструкторе класса происходит инициализация полей клетки значениями по умолчанию. Далее для упрощения реализации циклических переходов по состояниям мы используем вспомогательный список markSequence . Если клетка находится в состоянии "opened" , которое не входит в этот список, то в методе nextMark() ничего не произойдет, иначе клетка попадает в следующее состояние, причем, из последнего состояния "questioned" она "перепрыгивает" в начальное состояние "closed" . В методе open() мы проверяем состояние клетки, и если оно не равно "flagged" , то клетка переходит в открытое состояние "opened" .

Далее следует определение класса Модели MinesweeperModel . Метод startGame() осуществляет компоновку игрового поля по переданным ему параметрам rowCount , columnCount и mineCount . Для каждого из параметров происходит проверка на попадание в допустимый диапазон значений. Если переданное значение находится вне диапазона, то сохраняется то значение параметра игрового поля не меняется. Следует отметить, что для числа мин предусмотрена дополнительная проверка. Если переданное количество мин превышает размер поля, то мы ограничиваем его количеством клеток без единицы. Хотя, конечно, такая игра особого смысла не имеет и будет закончена в один шаг, поэтому вы можете придумать какое-нибудь свое правило на такой случай.

Игровое поле хранится в виде списка списков клеток в переменной cellsTable . Причем, обратите внимание, что в методе startGame() у клеток устанавливается лишь значение позиции, но мины еще не расставляются. Зато определяется переменная firstStep со значением True . Это нужно для того, чтобы убрать элемент случайности из первого хода и не допускать мгновенный проигрыш. Мины будут расставляться после первого хода в оставшихся клетках.

Метод getCell() просто возвращает клетку игрового поля по строке row и столбцу column . Если значение строки или столбца неверно, то возвращается None .

Метод isWin() возвращает True , если все оставшиеся не открытые клетки игрового поля заминированы, то есть в случае победы, иначе вернется False . А метод isGameOver() просто возвращает значение атрибута класса gameOver .

В методе openCell() происходит делегирование вызова open() объекту игровой клетки, которая расположена на игровом поле в позиции, указанной в параметрах метода. Если открытая клетка оказалось заминированной, то мы устанавливаем значение gameOver в True и выходим из метода. Если игра еще не окончена, то мы смотрим, а не первый ли это ход, проверяя значение firstStep . Если ход и правда первый, то произойдет расстановка мин по игровому полю с помощью вспомогательного метода generateMines() , о которой мы поговорим немного позже. Далее мы подсчитываем количество заминированных соседних клеток и устанавливаем соответствующее значение атрибута counter для обрабатываемой клетки. Если счетчик counter равен нулю, то мы запрашиваем список соседних клеток с помощью метода getCellNeighbours() и осуществляем рекурсивный вызов метода openCell() для всех закрытых "соседей", то есть для клеток со статусом "closed" .

Метод nextCellMark() всего лишь делегирует вызов методу nextMark() для клетки, расположенной на переданной позиции.

Расстановка мин происходит в методе generateMines() . Здесь мы просто случайным образом выбираем позицию на игровом поле и проверяем, чтобы клетка на этой позиции не была открыта и не была уже заминирована. Если оба условия выполнены, то мы устанавливаем значение атрибута mined равным True , иначе продолжаем поиск другой свободной клетки. Не забудьте, что для того, чтобы использовать на Python модуль random нужно явным образом его импортировать командой import random .

Метод подсчета количества мин countMinesAroundCell() вокруг некоторой клетки игрового поля полностью основывается на методе getCellNeighbours() . Запрос "соседей" клетки в методе getCellNeighbours() тоже реализован крайне просто. Не думаю, что у вас возникнут с ним проблемы.

Представление MinesweeperView

Теперь займемся представлением. Код класса MinesweeperView на Python представлен ниже:

Class MinesweeperView(Frame): def __init__(self, model, controller, parent = None): Frame.__init__(self, parent) self.model = model self.controller = controller self.controller.setView(self) self.createBoard() panel = Frame(self) panel.pack(side = BOTTOM, fill = X) Button(panel, text = "Новая игра", command = self.controller.startNewGame).pack(side = RIGHT) self.mineCount = StringVar(panel) self.mineCount.set(self.model.mineCount) Spinbox(panel, from_ = MIN_MINE_COUNT, to = MAX_MINE_COUNT, textvariable = self.mineCount, width = 5).pack(side = RIGHT) Label(panel, text = " Количество мин: ").pack(side = RIGHT) self.rowCount = StringVar(panel) self.rowCount.set(self.model.rowCount) Spinbox(panel, from_ = MIN_ROW_COUNT, to = MAX_ROW_COUNT, textvariable = self.rowCount, width = 5).pack(side = RIGHT) Label(panel, text = " x ").pack(side = RIGHT) self.columnCount = StringVar(panel) self.columnCount.set(self.model.columnCount) Spinbox(panel, from_ = MIN_COLUMN_COUNT, to = MAX_COLUMN_COUNT, textvariable = self.columnCount, width = 5).pack(side = RIGHT) Label(panel, text = "Размер поля: ").pack(side = RIGHT) def syncWithModel(self): for row in range(self.model.rowCount): for column in range(self.model.columnCount): cell = self.model.getCell(row, column) if cell: btn = self.buttonsTable[ row ][ column ] if self.model.isGameOver() and cell.mined: btn.config(bg = "black", text = "") if cell.state == "closed": btn.config(text = "") elif cell.state == "opened": btn.config(relief = SUNKEN, text = "") if cell.counter > 0: btn.config(text = cell.counter) elif cell.mined: btn.config(bg = "red") elif cell.state == "flagged": btn.config(text = "P") elif cell.state == "questioned": btn.config(text = "?") def blockCell(self, row, column, block = True): btn = self.buttonsTable[ row ][ column ] if not btn: return if block: btn.bind("", "break") else: btn.unbind("") def getGameSettings(self): return self.rowCount.get(), self.columnCount.get(), self.mineCount.get() def createBoard(self): try: self.board.pack_forget() self.board.destroy() self.rowCount.set(self.model.rowCount) self.columnCount.set(self.model.columnCount) self.mineCount.set(self.model.mineCount) except: pass self.board = Frame(self) self.board.pack() self.buttonsTable = for row in range(self.model.rowCount): line = Frame(self.board) line.pack(side = TOP) self.buttonsRow = for column in range(self.model.columnCount): btn = Button(line, width = 2, height = 1, command = lambda row = row, column = column: self.controller.onLeftClick(row, column), padx = 0, pady = 0) btn.pack(side = LEFT) btn.bind("", lambda e, row = row, column = column: self.controller.onRightClick(row, column)) self.buttonsRow.append(btn) self.buttonsTable.append(self.buttonsRow) def showWinMessage(self): showinfo("Поздравляем!", "Вы победили!") def showGameOverMessage(self): showinfo("Игра окончена!", "Вы проиграли!")

Наше Представление основано на классе Frame из модуля tkinter , поэтому не забудьте выполнить соответствующую команду импорта: from tkinter import * . В конструкторе класса передаются Модель и Контроллер. Сразу же вызывается метод createBoard() для компоновки игрового поля из клеток. Скажу заранее, что для этой цели мы будем использовать обычные кнопки Button . Затем создается Frame , который будет выполнять роль нижней панели для указания параметров игры. На эту панель мы последовательно помещаем кнопку "Новая игра", обработчиком которой становится наш Контроллер с его методом startNewGame() , а затем три счетчика Spinbox для того, чтобы игрок мог указать размер игрового поля и число мин.

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

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

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

Метод getGameSettings() всего лишь возвращает значения размещенных в нижней панели счетчиков с размером игрового поля и количеством мин.

Создание представления игрового поля осуществляется в методе createBoard() . В первую очередь идет попытка удаления старого игрового поля, если оно существовало, а также мы пробуем установить значения счетчиков из панели в соответствии с текущей конфигурацией Модели. Затем создается новый Frame , который мы назовем board , для представления игрового поля. Таблицу кнопок buttonsTable мы компонуем по тому же принципу, что и игровые клетки в Модели с помощью двойного цикла. Обработчики каждой кнопки привязываются к методам Контроллера onLeftClick() и onRightClick() для щелчка левой и правой кнопок мыши соответственно.

Последние два метода showWinMessage() и showGameOverMessage() всего лишь отображают диалоговые окна с соответствующими сообщениями с помощью функции showinfo() . Для того, чтобы ей воспользоваться вам понадобится импортировать еще один модуль: from tkinter.messagebox import * .

Контролер MinesweeperController

Вот мы и дошли до реализации Контроллера:

Class MinesweeperController: def __init__(self, model): self.model = model def setView(self, view): self.view = view def startNewGame(self): gameSettings = self.view.getGameSettings() try: self.model.startGame(*map(int, gameSettings)) except: self.model.startGame(self.model.rowCount, self.model.columnCount, self.model.mineCount) self.view.createBoard() def onLeftClick(self, row, column): self.model.openCell(row, column) self.view.syncWithModel() if self.model.isWin(): self.view.showWinMessage() self.startNewGame() elif self.model.isGameOver(): self.view.showGameOverMessage() self.startNewGame() def onRightClick(self, row, column): self.model.nextCellMark(row, column) self.view.blockCell(row, column, self.model.getCell(row, column).state == "flagged") self.view.syncWithModel()

Для привязки Представления к Контроллеру мы добавили метод setView() . Это объясняется тем, что если бы мы хотели передать Представление в конструктор, то это Представление должно было бы уже существовать до момента создания Контроллера. А тогда подобное решение с дополнительным методом для привязки просто перешло бы от Контроллера к Представлению, в которым бы появился метод setController() .

Метод-обработчик для нажатия на кнопке "Новая игра" startNewGame() сначала запрашивает параметры игры, введенные в Представление. Параметры игры возвращаются в виде кортежа из трех компонент, которые мы пытаемся преобразовать в int . Если все пройдет нормально, то мы передаем эти значения в метод Модели startGame() для построения игрового поля. Если же что-то пойдет не так, то мы просто пересоздадим игровое поле со старыми параметрами. А в завершении мы направляем запрос на создание нового отображения игрового поля в Представлении с помощью вызова метода createBoard() .

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

Щелчок правой кнопкой мыши обрабатывается в методе onRightClick() . В первой строке происходит вызов метода Модели nextCellMark() для циклической смены метки выбранной игровой клетки. В зависимости от нового состояния клетки Представлению отправляется запрос на установку или снятие блокировки на соответствующую кнопку. А в конце вновь обеспечивается обновление вида Представления для отображения актуального состояния Модели.

Комбинируем Модель, Представление и Контроллер

Теперь осталось лишь соединить все элементы в рамках нашей реализации Сапера на основе паттерна MVC и запустить игру:

Model = MinesweeperModel() controller = MinesweeperController(model); view = MinesweeperView(model, controller) view.pack() view.mainloop()

Заключение

Вот мы и рассмотрели паттерн MVC. Коротко прошлись по теории. А потом по шагам создали полноценное игровое приложение, пройдя путь от постановки задачи и проектирования архитектуры до реализации на языке программирования Python с использованием графического модуля tkinter .

Паттерн Model-View-Controller (MVC) , открытый в в конце 1970-х, представляет собой шаблон проектирования архитектуры программного обеспечения, основной задачей которого является отделение функций работы с данными от их представления. Теоретически, грамотно спроектированное MVC-приложение позволит фронтенд и бэкенд разработчикам в ходе работы не вмешиваться в зоны ответственности друг друга, то есть фронтенд-разработчику не понадобиться что-либо знать о «кухне» своего бэкенд-коллеги и наоборот.

Хотя изначально MVC был спроектирован для разработки десктоп-приложений, он был адаптирован для современных задач и пользуется у веб-разработчиков огромной популярностью, поскольку за счёт разделения ответственности стало возможным создавать более ясный, готовый к повторному использованию код. Паттерн MVC приводит к созданию ясных, модульных систем, что позволяет разработчикам очень быстро вносить изменения в существующий код.

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

Понимание MVC

Как уже было сказано, название паттерна происходит от аббревиатуры трёх слов: Model (модель), View (представление) и Controller (контроллер) . Вкратце принцип работы паттерна можно проиллюстрировать одной схемой ( можно найти на Википедии):

Эта схема наглядно показывает однонаправленность потока информации в паттерне, а также описывает роли каждого компонента.

Модель

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

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

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

Представление

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

Существуют некоторые заблуждения относительно предназначения представления, особенно в среде веб-разработчиков, которые только начинают строить свои приложения с использованием MVC. Одним из наиболее часто нарушаемых правил является то, что представление никоим образом не должно общаться с моделью , а все данные, получаемые представлением должны поступать только от контроллера . На практике же разработчики часто игнорируют эту концепцию, стоящую в основах MVC-паттерна. В статье Fabio Cevasco наглядно показан этот сбивающий с толку подход к MVC на примере фреймворка CakePHP, одним из многих нестандартных MVC-фреймворков:

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

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

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

Контроллер

Контроллер — это последняя часть связки MVC. Задачей контроллера является получение данных от пользователя и манипуляция моделью. Именно контроллер, и только он, является той частью системы, которая взаимодействует с пользователем.

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

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

MVC в PHP

Предлагаю попробовать реализовать описанное выше в небольшом приложении. Начнём с того, что создадим классы модели, представления и контроллера:

string = "MVC + PHP = Awesome!"; } } controller = $controller; $this->

" . $this->model->string . "

"; } } model = $model; } }

Основные классы готовы. Теперь давайте свяжем их вместе и запустим наше приложение:

output();

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

Давайте немного расширим приложение, добавив немного интерактивности, чтобы увидеть, как работает контроллер:

string = “MVC + PHP = Awesome, click here!”; } } controller = $controller; $this->model = $model; } public function output() { return "

model->string . "

"; } } model = $model; } public function clicked() { $this->model->string = “Updated Data, thanks to MVC and PHP!” } }

И в завершение немного модернизируем связующий код:

{$_GET["action"]}(); } echo $view->output();

Итоги

В этой небольшой статье мы рассмотрели основные концепции шаблона проектирования MVC и разработали простенькое приложение на его базе, хотя конечно, нам ещё очень далеко до того, чтобы использовать это в реальной жизни. В следующей статье мы рассмотрим основные затруднения, с которыми вы столкнётесь, если плотнее займётесь построением архитектуры приложения на базе MVC-паттерна. Stay tuned!

Разработка приложения в соответствии с шаблоном проектирования MVC (модель-представление-контроллёр) характерна для Java и применительно к DroidScript кажется непонятной и ненужной. Для чего всё усложнять? Ореол сложности и "магичности" MVC приобрёл по причине использования при его рассмотрении красивых, но непонятных слов (концепция, модель, бизнес-логика, паттерн) и сложных демонстраций в контексте Java. Всё намного проще: MVC - это один из шаблонов проектирования, при котором производится дополнительное разделении кода в объектно-ориентированной среде .

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

  • внешние данные из файлов и баз данных - метаданные, текст, графика, звуки, музыка и др.
  • внутренние данные приложения - строки с надписями на кнопках и других элементах управления, текст в диалоговых окнах, описание стилей, константы, программно генерируемая графика и др.

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

Рассмотрим для начала простой пример использования MVC в однофайловом приложении.

Однофайловая реализация MVC-модели

Возьмём простое приложение.

Function OnStart(){ var _lay = app.CreateLayout("linear", "VCenter,FillXY"); var _btnShowVersion = app.CreateButton("Показать версию", 0.3, 0.1); _btnShowVersion.SetBackColor("#66778976"); _btnShowVersion.SetMargins(0, 0.05, 0, 0); _btnShowVersion.SetOnTouch(function(){ _btnShowVersion.SetText("Версия приложения 1.0"); }); _lay.AddChild(_btnShowVersion); app.AddLayout(_lay); }

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

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

Одна из задач шаблона MVC как раз и состоит в разграничении доступа: сначала определяется модуль (или блок кода), являющийся источником ошибки, а затем даётся доступ только к нему. Для чего давать доступ к электронике и мотору автомобиля, если нужно заменить колесо?

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

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

  1. Модель
  2. Представление
  3. Контроллёр
//+++ модель (function(){ var _obj = ; //+++ данные var _version = "Версия приложения 1.0"; var _titleShowVersion = "Показать версию"; //--- данные
//+++ открытые методы для доступа к данным _obj.getVersion = function(){ return _version; } _obj.btnGetTitle = function(){ return _titleShowVersion; } //--- открытые методы для доступа к данным window.model = _obj; // открываем доступ к локальному объекту })(); //--- модель //+++ представление (function (){ var _lay = app.CreateLayout("linear", "VCenter,FillXY"); var _btnShowVersion = app.CreateButton(window.model.btnGetTitle(), 0.3, 0.1); _btnShowVersion.name = "_btnShowVersion"; _btnShowVersion.SetBackColor("#66778976"); _btnShowVersion.SetMargins(0, 0.05, 0, 0); _lay.AddChild(_btnShowVersion); app.AddLayout(_lay);

})(); //--- представление //+++ контроллёр (function(p_object){ var _obj = ; // открытый метод поиска объекта _obj.findObjectById = function(p_name){ var _objectList = app.GetObjects(); for (var _i in _objectList){ if(_objectList[_i].name == p_name){ return _objectList[ _i]; } } return null; } window.control = _obj; })(); function OnStart(){ var _buttonShowVersion = window.control.findObjectById("_btnShowVersion"); //+++ действие _buttonShowVersion.SetOnTouch(function(){ this.SetText(window.model.getVersion()); }); // --- действие } //--- контроллёр

Из-за разделения функций код приложения увеличился в несколько раз.

Изначально все переменные сделаны закрытыми и только в конце при необходимости открывается доступ к ним через глобальный объект window, что позволяет обойтись без глобальных переменных.

В примере реализован поиск виджета, как в Java, но можно поступить проще и сделать код более эффективным, открыв доступ к объекту через глобальный ассоциативный массив:

Window.controls = ;
window.controls.buttonShowVersion = _btnShowVersion;

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

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

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

Для более глубокого понимания преимуществ использования модели MVC рассмотрим разделение кода по отдельным файлам.

Трёхфайловая реализация MVC-модели

Разделение кода по разным файлам используется для более удобной работы с ним. Огромное количество мелких файлов, которые можно видеть в MVC проектах, может поставить под сомнение это утверждение, но видеть файлы - это одно, а работать с ними - совсем другое. В каждый момент времени разработчик взаимодействует с одним файлом из какого-то небольшого их множества. Для этого необходимо хорошо понимать структуру организации проекта и постоянно отслеживать тройку файлов - модель, представление и контроллёр, чтобы случайно не отредактировать сторонний код. Из-за ограничений редактора DroidScript такая группировка возможна только по именам файлов в корневой директории, например:

myproject_model.js - модель
myproject_view.js - представление
myproject_control.js - контроллёр

Ниже показан пример разделения кода предыдущего примера по файлам.

myproject_model.js - модель (function(){ var _obj = ; //+++ данные var _version = "Версия приложения 1.0"; //--- данные //+++ строковый ресурс var _titleShowVersion = "Показать версию"; //+++ строковый ресурс _obj.getVersion = function(){ return _version; } _obj.btnGetTitle = function(){ return _titleShowVersion; } window.model = _obj; })(); myproject_view.js - представление (function (){ var _lay = app.CreateLayout("linear", "VCenter,FillXY"); var _btnShowVersion = app.CreateButton(window.model.btnGetTitle(), 0.3, 0.1); _btnShowVersion.name = "_btnShowVersion"; _btnShowVersion.SetBackColor("#66778976"); _btnShowVersion.SetMargins(0, 0.05, 0, 0); _lay.AddChild(_btnShowVersion); app.AddLayout(_lay); })(); myproject_control.js - контроллёр app.LoadScript("myproject_model.js"); app.LoadScript("myproject_view.js"); (function(p_object){ var _obj = ; // метод поиска объекта _obj.findObjectById = function(p_name){ var _objectList = app.GetObjects(); for (var _i in _objectList){ if(_objectList[_i].name == p_name){ return _objectList[ _i]; } } return null; } window.control = _obj; })(); function OnStart(){ var _buttonShowVersion = window.control.findObjectById("_btnShowVersion"); //+++ действие _buttonShowVersion.SetOnTouch(function(){ this.SetText(window.model.getVersion()); }); // --- действие }

Такое простое разделение кода по файлам получилось не спроста. Для этого заранее была установлена связь с моделью через открытое свойство глобального корневого объекта - window.model , а связь с представлением через глобальный массив _map посредством метода app.GetObjects .

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

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

В JavaScript объекты передаются по ссылке. Изменение свойств виджета в контроллёре изменит свойства самого виджета. Теоретически, можно отделить объекты представлений от объектов кода, как это сделано в Java, где в качестве первых используются xml-структуры, но большого смысла в этом нет по двум причинам - отсутствия в DroidScript визуального редактора интерфейса и ограниченного набора доступных свойств API-объектов.

Многофайловая реализация MVC-модели

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

Номинал 5000 рублей

Номер (гс 8225497)

Имитация подлинной банкноты Банка России образца 1997 г.

Бумага. В УФ-лучах не люминесцирует

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

ПУБЛИЧНЫЕ ЗАЩИТНЫЕ ПРИЗНАКИ. ЛИЦЕВАЯ СТОРОНА

Водяные знаки (1,2)


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

Скрытое изображение (кипп-эффект)(3)


Не воспроизведено

Защитные волокна

Имитированы надпечаткой. В УФ-лучах наблюдается свечение зеленым и красным цветами.

Цветопеременная краска (4)


Изображение герба обладает повышенным блеском. Эффект OVI не воспроизведен

Элемент MVC+(5)


Не имитирован. Муаровые полосы MVC-эффекта наблюдаются при любом угле зрения; отобразились в процессе копирования с оригинала.

Микроперфорация(6)


Имитирована. Отверстия микроперфорации просматриваются в отраженном свете

Цветопеременный лак (7)

Эмблема Банка России покрыта золотистой краской. Поляризационный эффект не воспроизведен.

Бескрасочное тиснение (8)

Не имитировано

Рельефность

Не имитирована

Номинал 5000 рублей

Номер (гс 8225497)

Имитация подлинной банкноты Банка России образца 1997 года.

ПУБЛИЧНЫЕ ЗАЩИТНЫЕ ПРИЗНАКИ. ОБОРОТНАЯ СТОРОНА

Микротексты(9,10)


Частично воспроизведены

Защитная нить(11)


Имитирована веществом белого цвета на лицевой стороне, выходы имитированы тиснением фольгой. Выходы защитной нити слаборазличимы.

МАШИНОЧИТАЕМЫЕ ПРИЗНАКИ ЗАЩИТЫ

Люминесцентная защита

Частично имитирована

ИК-защита

Частично имитирована

Магнитная защита

Не имитирована

Примечание. По данным правоохранительных органов РФ, фальшивая банкнота изъята в Пермском крае.

Материал подготовлен ИПК «ИнтерКрим-пресс».

Лучшие статьи по теме