DroidScript
DroidScript
разработка мобильных приложений

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

DroidScript  
07.04.2017

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

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

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

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

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

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

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

// simple.js

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.

DroidScript  
© 2016-2022  Александр Страшко