Форум Flasher.ru
Ближайшие курсы в Школе RealTime
Список интенсивных курсов: [см.]  
  
Специальные предложения: [см.]  
  
 
Блоги Правила Справка Пользователи Календарь Поиск рулит! Сообщения за день Все разделы прочитаны
 

Вернуться   Форум Flasher.ru > Блоги > Rembrant

Оценить эту запись

Программа на pureMVC. Оно или нет?

Запись от Rembrant размещена 30.06.2014 в 18:47
Обновил(-а) Rembrant 03.07.2014 в 17:03

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

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

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

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

И главный вопрос - а энто точно pureMVC получилось?

Итак, дерево проекта:


Часть 1.
Самописный фреймворк, который теоретически можно подключать к совершенно другим проектам, занимает пакет mvc. Я отказался от обсерверов, как вещи совершенно непонятной, и вот что у меня осталось.
Классы Controller, Model и View - основные одиночки, которые организовывают работу с Командами, Посредниками и Медиаторами соответственно. В каждом классе есть 2 словаря:
- _listClass - пары ключ-класс, которые по ключу позволяют работать со своими представителями. Все ключи лежат в классе Constants;
- _listCommand, _listProxy, _listMediator - пары ключ-экземпляр класса, которые в данный момент живые.

Весь открытый функционал - 3 функции работы из представителями:
- registerCommand, registerProxy, registerMediator соответственно - создаёт в словаре пару ключ-класс для дальнейшего вызова;
- executeCommand, executeProxy, executeMediator - создают экземпляр класса по ключу и выполняют его. Могут передавать данные. Так как фактически у меня все экземпляры представителей - одиночки, я не пересоздаю класс перед выполнением, если он есть. Возможно, это неправильно и требует переработки;
- removeCommand, removeProxy, removeMediator - удаление (как класса, так и его экземпляров). removeMediator дополнительно вызывает destroy() у экземпляров Медиаторов.

Дополнительно в Модели есть getProxy() для доступа к Представителям по ключу - для получения от них данных.

Естественно, всё это счастье держится на интерфейсах.

Controller.as:
Код AS3:
package mvc.core 
{
	import flash.utils.Dictionary;
	import mvc.interfaces.IController;
	import mvc.pattern.BaseNotifier;
 
	public class Controller implements IController
	{
 
		public function Controller() 
		{
			if (instance != null)
				throw new Error("This class is singleton");
			instance = this;
		}
 
		//--------------------------------------------------------------------------
		//
		// PUBLIC SECTION 
		//
		//--------------------------------------------------------------------------
		public static function getInstance():IController {
			if (instance == null)
				instance = new Controller();
			return instance;
		}
 
		public function registerCommand(id:String, classId:Class):void
		{
			_listClass[id] = classId;
		}
 
		public function executeCommand(id:String, data:Object):void
		{
			if (_listClass[id])
			{
				var classId:Class = _listClass[id];
				var command:BaseNotifier = _listCommand[id] != null ? _listCommand[id] : new classId();
				_listCommand[id] = command;
				if (data)
					command.execute(data);
				else
					command.execute();
			}
		}
 
		public function removeCommand(id:String):void
		{
			_listCommand[id] = null;
			delete _listCommand[id];
			delete _listClass[id];
		}
 
		//--------------------------------------------------------------------------
		//
		// PROTECTED SECTION 
		//
		//--------------------------------------------------------------------------
		protected static var instance:IController;
 
		//--------------------------------------------------------------------------
		//
		// PRIVATE SECTION 
		//
		//--------------------------------------------------------------------------
		private var _listClass:Dictionary = new Dictionary();
		private var _listCommand:Dictionary = new Dictionary();
	}
}
Model.as:
Код AS3:
package mvc.core 
{
	import flash.utils.Dictionary;
	import mvc.interfaces.IModel;
	import mvc.pattern.BaseNotifier;
 
	public class Model implements IModel
	{
		public function Model() 
		{
			if (instance != null)
				throw new Error("This class is singleton");
			instance = this;
		}
 
		//--------------------------------------------------------------------------
		//
		// PUBLIC SECTION 
		//
		//--------------------------------------------------------------------------
		public static function getInstance():IModel {
			if (instance == null)
				instance = new Model();
			return instance;
		}
 
		public function registerProxy(id:String, classId:Class):void
		{
			_listClass[id] = classId;
		}
 
		public function executeProxy(id:String, data:Object):void
		{
			if (_listClass[id])
			{
				var classId:Class = _listClass[id];
				var proxy:BaseNotifier = _listProxy[id] != null ? _listProxy[id] : new classId();
				_listProxy[id] = proxy;
				if (data)
					proxy.execute(data);
				else
					proxy.execute();
			}
		}
 
		public function removeProxy(id:String):void
		{
			_listProxy[id] = null;
			delete _listProxy[id];
			delete _listClass[id];
		}
 
		public function getProxy(id:String):Object
		{
			return _listProxy[id];
		}
 
		//--------------------------------------------------------------------------
		//
		// PROTECTED SECTION 
		//
		//--------------------------------------------------------------------------
		protected static var instance:IModel;
 
		//--------------------------------------------------------------------------
		//
		// PRIVATE SECTION 
		//
		//--------------------------------------------------------------------------
		private var _listClass:Dictionary = new Dictionary();
		private var _listProxy:Dictionary = new Dictionary();
	}
}
View.as:
Код AS3:
package mvc.core 
{
	import flash.utils.Dictionary;
	import mvc.interfaces.IView;
	import mvc.pattern.BaseNotifier;
 
	public class View implements IView
	{
		public function View() 
		{
			if (instance != null)
				throw new Error("This class is singleton");
			instance = this;
		}
 
		//--------------------------------------------------------------------------
		//
		// PUBLIC SECTION 
		//
		//--------------------------------------------------------------------------
		public static function getInstance():IView {
			if (instance == null)
				instance = new View();
			return instance;
		}
 
		public function registerMediator(id:String, classId:Class):void
		{
			_listClass[id] = classId;
		}
 
		public function executeMediator(id:String, data:Object):void
		{
			if (_listClass[id])
			{
				var classId:Class = _listClass[id];
				var mediator:BaseNotifier = _listMediator[id] != null ? _listMediator[id] : new classId();
				_listMediator[id] = mediator;
				if (data)
					mediator.execute(data);
				else
					mediator.execute();
			}
		}
 
		public function removeMediator(id:String):void
		{
			_listMediator[id].destroy();
			_listMediator[id] = null;
			delete _listMediator[id];
			delete _listClass[id];
		}
 
		//--------------------------------------------------------------------------
		//
		// PROTECTED SECTION 
		//
		//--------------------------------------------------------------------------
		protected static var instance:IView;
 
		//--------------------------------------------------------------------------
		//
		// PRIVATE SECTION 
		//
		//--------------------------------------------------------------------------
		private var _listClass:Dictionary = new Dictionary();
		private var _listMediator:Dictionary = new Dictionary();
	}
}
Далее во фреймворке должны лежать базовые классы представителей - Command, View, Mediator. Но поскольку у меня они практически одинаковы, я объединяю их в один класс. У него есть execute() и destroy(). Последнее нужно только для медиаторов - отписываться от слушателей и чистить графику. Кроме того, все представители могут стучаться к фасаду, для этого у них есть notify().

BaseNotifier.as:
Код AS3:
package mvc.pattern 
{
	import mvc.Facade;
	import mvc.interfaces.IBaseNotifier;
	import mvc.interfaces.IFacade;
 
	public class BaseNotifier implements IBaseNotifier
	{
		//base class for: Command, View, Mediator
		public function BaseNotifier() { }
 
		//--------------------------------------------------------------------------
		//
		// PUBLIC SECTION 
		//
		//--------------------------------------------------------------------------
		public function execute(data:Object = null):void { }
 
		public function destroy():void { }
 
		//--------------------------------------------------------------------------
		//
		// PROTECTED SECTION 
		//
		//--------------------------------------------------------------------------
		protected var facade:IFacade = Facade.getInstance();
 
		protected function notify(notifyId:String, data:Object = null):void
		{
			facade.handleNotify(notifyId, data);
		}
	}
}
Так плавно мы подошли к Фасаду. Фасад имеет статический getInstance(), через который его можно дёрнуть из любой точки приложения. На старте он инициализирует наши Controller, Model и View. Кроме использованного в BaseNotifier handleNotify(), он предоставляет полный доступ ко всем публичным функциям составляющих MVC: register, execute, remove.

Facade.as:
Код AS3:
package mvc 
{
	import mvc.core.Controller;
	import mvc.core.Model;
	import mvc.core.View;
	import mvc.interfaces.IController;
	import mvc.interfaces.IFacade;
	import mvc.interfaces.IModel;
	import mvc.interfaces.IView;
 
	public class Facade implements IFacade
	{
		public function Facade() 
		{
			if (instance != null)
				throw new Error("This class is singleton");
			instance = this;
			initialize();
		}
 
		//--------------------------------------------------------------------------
		//
		// PUBLIC SECTION 
		//
		//--------------------------------------------------------------------------
		public static function getInstance():IFacade {
			if (instance == null)
				instance = new Facade();
			return instance;
		}
 
		public function handleNotify(notifyId:String, data:Object = null):void
		{
			throw new Error("function must be overrided");
		}
 
		public function registerCommand(id:String, classId:Class):void
		{
			_controller.registerCommand(id, classId);
		}
 
		public function executeCommand(id:String, data:Object = null):void
		{
			_controller.executeCommand(id, data);
		}
 
		public function removeCommand(id:String):void
		{
			_controller.removeCommand(id);
		}
 
		public function registerProxy(id:String, classId:Class):void
		{
			_model.registerProxy(id, classId);
		}
 
		public function executeProxy(id:String, data:Object = null):void
		{
			_model.executeProxy(id, data);
		}
 
		public function removeProxy(id:String):void
		{
			_model.removeProxy(id);
		}
 
		public function getProxy(id:String):Object
		{
			return _model.getProxy(id);
		}
 
		public function registerMediator(id:String, classId:Class):void
		{
			_view.registerMediator(id, classId);
		}
 
		public function executeMediator(id:String, data:Object = null):void
		{
			_view.executeMediator(id, data);
		}
 
		public function removeMediator(id:String):void
		{
			_view.removeMediator(id);
		}
 
		//--------------------------------------------------------------------------
		//
		// PROTECTED SECTION 
		//
		//--------------------------------------------------------------------------
		protected static var instance:IFacade;
 
		//--------------------------------------------------------------------------
		//
		// PRIVATE SECTION 
		//
		//--------------------------------------------------------------------------
		private var _model:IModel;
		private var _controller:IController;
		private var _view:IView;
 
		private function initialize():void
		{
			if (!_model)
				_model = Model.getInstance();
			if (!_controller)
				_controller = Controller.getInstance();
			if (!_view)
				_view = View.getInstance();
		}
	}
}
Часть 2.
Собственно наша галерея - пакет app. Базируется на частном Фасаде AppFacade, который наследуется от Facade. Для старта нужно всего лишь запустить его в Main.as:

Код AS3:
// entry point
_facade = AppFacade.getInstance();
_facade.stage = stage;
_facade.start();
AppFacade на старте регистрирует и выполняет первую команду. Кроме того он замещает абстрактную функцию - обработчик уведомлений handleNotify. Чтобы никого не испугать, сначала я оставлю её пустой.

AppFacade.as:
Код AS3:
package app 
{
	import app.controller.ClosePreviewCommand;
	import app.controller.LoadFilesCommand;
	import app.controller.LoadPreviewCommand;
	import app.controller.ShowMainCommand;
	import app.controller.ShowPreviewCommand;
	import dicts.Constants;
	import flash.display.Bitmap;
	import flash.display.Stage;
	import mvc.Facade;
 
	public class AppFacade extends Facade
	{
		public function AppFacade() { }
 
		//--------------------------------------------------------------------------
		//
		// PUBLIC SECTION 
		//
		//--------------------------------------------------------------------------
		public static function getInstance():AppFacade 
        {
            if (instance == null) instance = new AppFacade();
            return instance as AppFacade;
        }
 
		public function start():void
		{
			registerCommand(Constants.COMMAND_LOAD_FILES, LoadFilesCommand);
			executeCommand(Constants.COMMAND_LOAD_FILES);
		}
 
		public function set stage(value:Stage):void
		{
			_stage = value;
		}
 
		public function get stage():Stage
		{
			return _stage;
		}
 
		override public function handleNotify(notifyId:String, data:Object = null):void
		{
 
		}
 
		//--------------------------------------------------------------------------
		//
		// PRIVATE SECTION 
		//
		//--------------------------------------------------------------------------
		private var _stage:Stage;
	}
}
Мы добрались к первой команде (функция start). Она даёт отмашку соответствующему Прокси, который должен загрузить все файлы по предоставленным ссылкам.

LoadFilesCommand.as:
Код AS3:
package app.controller 
{
	import app.model.LoadAllProxy;
	import dicts.Constants;
	import mvc.interfaces.IBaseNotifier;
	import mvc.pattern.BaseNotifier;
 
	public class LoadFilesCommand extends BaseNotifier implements IBaseNotifier
	{
		public function LoadFilesCommand() { }
 
		override public function execute(data:Object = null):void
		{
			facade.registerProxy(Constants.PROXY_LOAD_ALL, LoadAllProxy);
			facade.executeProxy(Constants.PROXY_LOAD_ALL, Constants.FILES_LIST);
		}
	}
}
LoadAllProxy - выполняет загрузку всех файлов и по её завершении делает уведомление Фасаду.

LoadAllProxy.as:
Код AS3:
package app.model 
{
	import dicts.Constants;
	import flash.display.Loader;
	import flash.display.LoaderInfo;
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.net.URLRequest;
	import mvc.interfaces.IBaseNotifier;
	import mvc.pattern.BaseNotifier;
 
	public class LoadAllProxy extends BaseNotifier implements IBaseNotifier
	{
		public function LoadAllProxy() { }
 
		//--------------------------------------------------------------------------
		//
		// PUBLIC SECTION 
		//
		//--------------------------------------------------------------------------
		override public function execute(data:Object = null):void
		{
			_fileList = data as Vector.<String>;
			_total = _fileList.length;
			var loader:Loader;
			for (var i:int = 0; i < _total; i++)
			{
				loader = new Loader();
				loader.contentLoaderInfo.addEventListener(Event.COMPLETE, imageLoadHandler);
				loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
				loader.load(new URLRequest(_fileList[i]));
			}
		}
 
		//--------------------------------------------------------------------------
		//
		// PRIVATE SECTION 
		//
		//--------------------------------------------------------------------------
		private var _fileList:Vector.<String>;
		private var _total:uint;
		private var _loaded:uint = 0;
		private var _images:Array = [];
 
		private function imageLoadHandler(event:Event):void
		{
			var info:LoaderInfo = LoaderInfo(event.currentTarget);
			_images[Constants.FILES_LIST.indexOf(info.url)] = info.content;
			info.loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, imageLoadHandler);
			info.loader.contentLoaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, errorHandler);
			_loaded++;
			if (_loaded >= _total)
				notify(Constants.NOTIFY_LOAD_COMPLETE, { images:_images } );
		}
 
		private function errorHandler(event:ErrorEvent):void
		{
			throw new Error("bad link or internet disconnect");
		}
	}
}
Теперь начнём заполнять функцию handleNotify Фасада. Я решил представить её в виде обычного switch-case по ключу уведомлений. После добавления первой обработки она принимает вид:
Код AS3:
override public function handleNotify(notifyId:String, data:Object = null):void
{
	switch(notifyId)
	{
		case Constants.NOTIFY_LOAD_COMPLETE:
		{
			var images:Array = data.images as Array;
			removeCommand(Constants.COMMAND_LOAD_FILES);
			registerCommand(Constants.COMMAND_SHOW_MAIN, ShowMainCommand);
			executeCommand(Constants.COMMAND_SHOW_MAIN, { images:images } );
			break;
		}
		default:
		{
			throw new Error("unknown notify");
		}
	}
}
Фасад избавляется от отработанной команды и вызывает следующую, которая должна открыть главную панель с миниатюрами картинок.

ShowMainCommand.as:
Код AS3:
package app.controller 
{
	import app.view.MainPanelMediator;
	import dicts.Constants;
	import mvc.interfaces.IBaseNotifier;
	import mvc.pattern.BaseNotifier;
 
	public class ShowMainCommand extends BaseNotifier implements IBaseNotifier
	{
		public function ShowMainCommand() { }
 
		override public function execute(data:Object = null):void
		{
			facade.removeProxy(Constants.PROXY_LOAD_ALL);
			facade.registerMediator(Constants.MEDIATOR_MAIN_PANEL, MainPanelMediator);
			facade.executeMediator(Constants.MEDIATOR_MAIN_PANEL, { images:data.images } );
		}
	}
}
Загруженные изображения в виде массива битмапов тянутся через Фасад в команду, а уже она передаёт их в Медиатор.
Медиатор - это такой апатичный товарищ, который создаёт тайлы с миниатюрами, абсолютно не интересуясь, откуда они. Так само безразлично он сообщает Фасаду о клике по тайлу, совершенно не интересуясь, что тот будет с этим делать (функция tileClickHandler).

MainPanelMediator.as:
Код AS3:
package app.view 
{
	import app.AppFacade;
	import dicts.Constants;
	import flash.display.Stage;
	import flash.events.MouseEvent;
	import mvc.interfaces.IBaseNotifier;
	import mvc.pattern.BaseNotifier;
 
	public class MainPanelMediator extends BaseNotifier implements IBaseNotifier
	{
		public function MainPanelMediator() { }
 
		//--------------------------------------------------------------------------
		//
		// PUBLIC SECTION 
		//
		//--------------------------------------------------------------------------
		override public function execute(data:Object = null):void
		{
			_stage = AppFacade.getInstance().stage;
			_images = data.images;
 
			_tilesList = new Vector.<MainPanelTile>();
			var length:uint = _images.length;
			for (var i:int = 0; i < length; i++)
			{
				_tilesList[i] = new MainPanelTile(_images[i]);
				_tilesList[i].addEventListener(MouseEvent.CLICK, tileClickHandler);
				_tilesList[i].x = 10 + ((_tilesList.length - 1) % COLUMN) * WIDTH;
				_tilesList[i].y = 10 + int((_tilesList.length - 1) / COLUMN) * HEIGHT;
				_stage.addChild(_tilesList[i]);
			}
		}
 
		override public function destroy():void
		{
			for each (var tile:MainPanelTile in _tilesList)
			{
				tile.removeEventListener(MouseEvent.CLICK, tileClickHandler);
				tile.parent.removeChild(tile);
				tile.destroy();
				tile = null;
			}
			_tilesList = null;
		}
 
		//--------------------------------------------------------------------------
		//
		// PRIVATE SECTION 
		//
		//--------------------------------------------------------------------------
		private static const COLUMN:uint = 4;
		private static const WIDTH:uint = 200;
		private static const HEIGHT:uint = 160;
 
		private var _stage:Stage;
		private var _images:Array;
		private var _tilesList:Vector.<MainPanelTile>;
 
		private function tileClickHandler(event:MouseEvent):void
		{
			var index:int = _tilesList.indexOf(MainPanelTile(event.currentTarget));
			notify(Constants.NOTIFY_TILE_CLICK, { index:index } );
		}
	}
}
По клике на миниатюру необходимо открыть её превью. Для этого Фасад создаёт в handleNotify новую команду, в которую передаётся индекс нажатого тайла:
Код AS3:
case Constants.NOTIFY_TILE_CLICK:
{
	var index:int = data.index;
	removeCommand(Constants.COMMAND_SHOW_MAIN);
	registerCommand(Constants.COMMAND_LOAD_PREVIEW, LoadPreviewCommand);
	executeCommand(Constants.COMMAND_LOAD_PREVIEW, { index:index } );
	break;
}
Команда дёргает следующий прокси - LoadPreviewProxy.

LoadPreviewCommand.as:
Код AS3:
package app.controller 
{
	import app.model.LoadPreviewProxy;
	import dicts.Constants;
	import mvc.interfaces.IBaseNotifier;
	import mvc.pattern.BaseNotifier;
 
	public class LoadPreviewCommand extends BaseNotifier implements IBaseNotifier
	{
		public function LoadPreviewCommand() { }
 
		override public function execute(data:Object = null):void
		{
			facade.registerProxy(Constants.PROXY_LOAD_PREVIEW, LoadPreviewProxy);
			facade.executeProxy(Constants.PROXY_LOAD_PREVIEW, { url:Constants.FILES_LIST[data.index], index:data.index } );
		}
	}
}
LoadPreviewProxy напоминает LoadAllProxy, но грузит только одну картинку. По завершении также стучит в Фасад.

Кроме того, чтобы не тянуть соплёй индекс через все классы, я оставляю его здесь и даю доступ на чтение. Вот здесь и понадобится getProxy() из Model.

LoadPreviewProxy.as:
Код AS3:
package app.model 
{
	import dicts.Constants;
	import flash.display.Loader;
	import flash.display.LoaderInfo;
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.net.URLRequest;
	import mvc.interfaces.IBaseNotifier;
	import mvc.pattern.BaseNotifier;
 
	public class LoadPreviewProxy extends BaseNotifier implements IBaseNotifier
	{
		public function LoadPreviewProxy() { }
 
		//--------------------------------------------------------------------------
		//
		// PUBLIC SECTION 
		//
		//--------------------------------------------------------------------------
		override public function execute(data:Object = null):void
		{
			_index = data.index;
			var loader:Loader = new Loader();
			loader.contentLoaderInfo.addEventListener(Event.COMPLETE, imageLoadHandler);
			loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
			loader.load(new URLRequest(data.url));
		}
 
		public function get index():int
		{
			return _index;
		}
 
		//--------------------------------------------------------------------------
		//
		// PRIVATE SECTION 
		//
		//--------------------------------------------------------------------------
		private var _index:int;
 
		private function imageLoadHandler(event:Event):void
		{
			var info:LoaderInfo = LoaderInfo(event.currentTarget);
			notify(Constants.NOTIFY_LOAD_PREVIEW_COMPLETE, { image:info.content } );
		}
 
		private function errorHandler(event:ErrorEvent):void
		{
			throw new Error("bad link or internet disconnect");
		}
	}
}
Теперь у нас есть битмап превьюшки, и Фасад передаёт её в следующую команду:
Код AS3:
case Constants.NOTIFY_LOAD_PREVIEW_COMPLETE:
{
	var image:Bitmap = data.image;
	removeCommand(Constants.COMMAND_LOAD_PREVIEW);
	registerCommand(Constants.COMMAND_SHOW_PREVIEW, ShowPreviewCommand);
	executeCommand(Constants.COMMAND_SHOW_PREVIEW, { image:image } );
	break;
}
Эта команда регистрирует и запускает второй медиатор - Превью.

ShowPreviewCommand.as:
Код AS3:
package app.controller 
{
	import app.view.PreviewMediator;
	import dicts.Constants;
	import mvc.interfaces.IBaseNotifier;
	import mvc.pattern.BaseNotifier;
 
	public class ShowPreviewCommand extends BaseNotifier implements IBaseNotifier
	{
		public function ShowPreviewCommand() { }
 
		override public function execute(data:Object = null):void
		{
			var index:int = facade.getProxy(Constants.PROXY_LOAD_PREVIEW).index;
			facade.removeProxy(Constants.PROXY_LOAD_PREVIEW);
			facade.registerMediator(Constants.MEDIATOR_PREVIEW, PreviewMediator);
			facade.executeMediator(Constants.MEDIATOR_PREVIEW, { image:data.image, index:index, total:Constants.FILES_LIST.length } );
		}
	}
}
PreviewMediator отображает превью изображения на весь экран приложения. Он содержит кнопки Вперёд-Назад, позволяющие двигаться по галерее (для проверки доступности кнопок ему передаётся index и total). Клик по фону закрывает просмотрщик.

Медиатор построен так, что по вызову execute() он мог создать весь свой вид (кнопки и фон) или только обновить изображение (если кнопки и фон уже есть). Таким образом, при кликах на Вперёд-Назад, его не нужно пересоздавать.

По нажатию на кнопки он вызывает уже использованное ранее уведомление к Фасаду NOTIFY_TILE_CLICK с новым индексом. По нажатию на фон - новое уведомление NOTIFY_CLOSE_PREVIEW.

PreviewMediator.as:
Код AS3:
package app.view 
{
	import app.AppFacade;
	import dicts.Constants;
	import flash.display.Bitmap;
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.events.MouseEvent;
	import mvc.interfaces.IBaseNotifier;
	import mvc.pattern.BaseNotifier;
 
	public class PreviewMediator extends BaseNotifier implements IBaseNotifier
	{
		public function PreviewMediator() { }
 
		//--------------------------------------------------------------------------
		//
		// PUBLIC SECTION 
		//
		//--------------------------------------------------------------------------
		override public function execute(data:Object = null):void
		{
			if (_image && _image.parent)
				_image.parent.removeChild(_image);
			_image = null;
 
			_stage = AppFacade.getInstance().stage;
			_image = data.image;
			_index = data.index;
			var total:int = data.total;
 
			if (!_template)
			{
				_template = new Sprite();
				_template.graphics.beginFill(0x000000, 0.6);
				_template.graphics.drawRect(0, 0, _stage.stageWidth, _stage.stageHeight);
				_template.graphics.endFill();
				_template.addEventListener(MouseEvent.CLICK, closeClickHandler);
				_stage.addChild(_template);
 
				_btnBack = new BtnBackTemplate();
				_btnBack.buttonMode = true;
				_btnBack.addEventListener(MouseEvent.CLICK, backClickHandler);
				_btnBack.x = 0;
				_btnBack.y = (_stage.stageHeight - _btnBack.height) / 2;
				_stage.addChild(_btnBack);
 
				_btnNext = new BtnNextTemplate();
				_btnNext.buttonMode = true;
				_btnNext.addEventListener(MouseEvent.CLICK, nextClickHandler);
				_btnNext.x = _stage.stageWidth - _btnNext.width;
				_btnNext.y = (_stage.stageHeight - _btnNext.height) / 2;
				_stage.addChild(_btnNext);
			}
			_btnBack.alpha = _index > 0 ? 1 : 0.4;
			_btnNext.alpha = _index + 1 < total ? 1 : 0.4;
 
			var diff:Number = Math.min(_stage.stageWidth / _image.width, _stage.stageHeight / _image.height);
			_image.width *= diff;
			_image.height *= diff;
			_image.smoothing = true;
			_image.x = (_stage.stageWidth - _image.width) / 2;
			_image.y = (_stage.stageHeight - _image.height) / 2;
			_template.addChild(_image);
		}
 
		override public function destroy():void
		{
			if (_btnBack)
			{
				_btnBack.parent.removeChild(_btnBack);
				_btnBack.removeEventListener(MouseEvent.CLICK, backClickHandler);
				_btnBack = null;
			}
			if (_btnNext)
			{
				_btnNext.parent.removeChild(_btnNext);
				_btnNext.removeEventListener(MouseEvent.CLICK, nextClickHandler);
				_btnNext = null;
			}
			if (_image && _image.parent)
				_image.parent.removeChild(_image);
			_image = null;
			if (_template)
			{
				_template.removeEventListener(MouseEvent.CLICK, closeClickHandler);
				_template.parent.removeChild(_template);
				_template = null;
			}
		}
 
		//--------------------------------------------------------------------------
		//
		// PRIVATE SECTION 
		//
		//--------------------------------------------------------------------------
		private var _stage:Stage;
		private var _image:Bitmap;
		private var _template:Sprite;
		private var _btnBack:BtnBackTemplate;
		private var _btnNext:BtnNextTemplate;
		private var _index:int;
 
		private function disableBtns():void
		{
			_btnBack.alpha = _btnNext.alpha = 0.4;
		}
 
		private function backClickHandler(event:MouseEvent):void
		{
			if (event.currentTarget.alpha < 1)
				return;
			disableBtns();
			notify(Constants.NOTIFY_TILE_CLICK, { index:_index - 1 } );
		}
 
		private function nextClickHandler(event:MouseEvent):void
		{
			if (event.currentTarget.alpha < 1)
				return;
			disableBtns();
			notify(Constants.NOTIFY_TILE_CLICK, { index:_index + 1 } );
		}
 
		private function closeClickHandler(event:MouseEvent):void
		{
			notify(Constants.NOTIFY_CLOSE_PREVIEW);
		}
	}
}
Добавляем обработку последнего уведомления в Фасад. Окончательный вид функции handleNotify:
Код AS3:
override public function handleNotify(notifyId:String, data:Object = null):void
{
	switch(notifyId)
	{
		case Constants.NOTIFY_LOAD_COMPLETE:
		{
			var images:Array = data.images as Array;
			removeCommand(Constants.COMMAND_LOAD_FILES);
			registerCommand(Constants.COMMAND_SHOW_MAIN, ShowMainCommand);
			executeCommand(Constants.COMMAND_SHOW_MAIN, { images:images } );
			break;
		}
		case Constants.NOTIFY_TILE_CLICK:
		{
			var index:int = data.index;
			removeCommand(Constants.COMMAND_SHOW_MAIN);
			registerCommand(Constants.COMMAND_LOAD_PREVIEW, LoadPreviewCommand);
			executeCommand(Constants.COMMAND_LOAD_PREVIEW, { index:index } );
			break;
		}
		case Constants.NOTIFY_LOAD_PREVIEW_COMPLETE:
		{
			var image:Bitmap = data.image;
			removeCommand(Constants.COMMAND_LOAD_PREVIEW);
			registerCommand(Constants.COMMAND_SHOW_PREVIEW, ShowPreviewCommand);
			executeCommand(Constants.COMMAND_SHOW_PREVIEW, { image:image } );
			break;
		}
		case Constants.NOTIFY_CLOSE_PREVIEW:
		{
			removeCommand(Constants.COMMAND_SHOW_PREVIEW);
			registerCommand(Constants.COMMAND_CLOSE_PREVIEW, ClosePreviewCommand);
			executeCommand(Constants.COMMAND_CLOSE_PREVIEW);
			removeCommand(Constants.COMMAND_CLOSE_PREVIEW);
			break;
		}
		default:
		{
			throw new Error("unknown notify");
		}
	}
}
И последняя команда, сносящая Медиатор превью.

ClosePreviewCommand.as:
Код AS3:
package app.controller 
{
	import dicts.Constants;
	import mvc.interfaces.IBaseNotifier;
	import mvc.pattern.BaseNotifier;
 
	public class ClosePreviewCommand extends BaseNotifier implements IBaseNotifier
	{
		public function ClosePreviewCommand() { }
 
		override public function execute(data:Object = null):void
		{
			facade.removeMediator(Constants.MEDIATOR_PREVIEW);
		}
	}
}
Вот и всё.


Итого, вместе с интерфейсами имеем "всего" 23 класса (что всё равно раза в 2 меньше, чем в проекте - конкуренте).

Предположим, что галерею необходимо дополнить - сделать главную панель на несколько страниц. Для этого нужно изменить следующее:
- LoadAllProxy грузит не все файлы, а с m по n (передаём это через LoadFilesCommand);
- ShowMainCommand передаёт дополнительно m и n в MainPanelMediator;
- в MainPanelMediator добавляются 2 кнопки, активность которых определяется по m и n. При клике они вызывают LoadFilesCommand с новыми индексами. Круг замкнулся.
То есть одна из главных задач фреймворка - изменения вносятся легко - вроде бы выполняется.

И если кто дочитал до этого места, а тем более что-то понял, пару вопросов в завершение.
1) зачем в принципе нужны Команды, если несколько строк, из которых они состоят, было бы проще выделить в функции Фасада, а не плодить классы?
2) как решить проблему доступа к глобальным данным? Тот же stage пришлось передавать из Фасада в 100500 мест, а нельзя ли сделать на неё (и все подобные данные) геттер прямо в Фасаде?

Исходники прилагаются.

UPD1: по первым комментариям, выпилил действительно неудачный момент - передачу stage через кучу классов, оставив на неё одну глобальную ссылку. Обновлённый вариант - source2.zip.

UPD2: Вставил вместо своего фреймворка скачанный из сети puremvc, подогнал свои классы. Заняло часа 2 (ожидал, что будет хуже). Правда, так и обошлось без EventDispatcher'ов - с ихними нотификациями получилось даже удобнее, чем с моим велосипедом.
Исходники перезаливаю в source_canonic, а старый код в статье пусть остаётся мне в назидание.
Вложения
Тип файла: zip source.zip (45.5 Кб, 118 просмотров)
Тип файла: zip source2.zip (45.5 Кб, 115 просмотров)
Тип файла: zip source_canonic.zip (73.6 Кб, 117 просмотров)
Всего комментариев 20

Комментарии

Старый 01.07.2014 13:09 in4core вне форума
in4core
 
Аватар для in4core
Цитата:
Итого, вместе с интерфейсами имеем "всего" 23 класса
Собственно говоря этим все и сказано, используем 23 класса, чтобы создать 3 картинки. 2-3 класса - это максимум.
Команды обычно для работы с сервером, запрос - ответ. В игровых движках так же могут быть команды.
Глобальные данные не нужны иначе, это значит, что неверно построена архитектура. Глобальным может быть например конфиг, где 100500 констант и только. Это естественно статик. Но передавать стейдж куда то там.... это не МВС.
Да и вообще, не зря же люди хают pureMVC как таковой! От балды мнения не берутся ) ПРислушайтесь )
Старый 01.07.2014 13:40 Rembrant вне форума
Rembrant
 
Аватар для Rembrant
Цитата:
Но передавать стейдж куда то там.... это не МВС.
то есть в данном случае было бы правильно зарегестрировать Медиаторы при старте Фасада (с передачей им stage)?
Старый 01.07.2014 14:01 dimarik вне форума
dimarik
 
Аватар для dimarik
Есть "теория шести рукопожатий". Я её стараюсь придерживаться и тем самым проблема доступности stage и подобных глобальных объектов немного отступает. По сути не нужно передавать ссылку на него в каждый объект, нужно лишь добраться до объекта, имеющего на него ссылку.
Старый 01.07.2014 14:46 Rembrant вне форума
Rembrant
 
Аватар для Rembrant
dimarik, ну доступ к объекту со stage'м - Фасада, есть у всех. Только как я понял из первого комментария, это будет не канонично по puremvc.
Старый 01.07.2014 17:20 dendrit вне форума
dendrit
 
Аватар для dendrit
Честно говоря, я не понимаю, зачем нужен pureMVC. Да, я начинал сразу с Flex и, соответственно, c AS3.
Зачем усложнять себе жизнь и отказываться от MXML, Data Binding?
Паттерн Presentation Model очень хорошо сочетается с каким-нибудь Dependency Injection (сам я пользуюсь Parsley).
Модель отправляет событие, фреймворк запускает команду по событию, передает ответ в модель.
Старый 01.07.2014 22:45 in4core вне форума
in4core
 
Аватар для in4core
dendrit - тут видишь в чем дело - требуется на работе. А это действительно так, многие конторы работают на нем. У них уже привычная четкая позиция. Фрилансеры же в большинтсве своем работают на собственных MVC.

Цитата:
то есть в данном случае было бы правильно зарегестрировать Медиаторы при старте Фасада (с передачей им stage)?
Думаю стоит послушать dimarik в этом случае. У меня лично в сборках нет таких мест, где требуется ПЕРЕДАТЬ стейдж. Оно по сути и не должно появляться таких мест. Все эти медиаторы и фасады - вода. Нужно подходить к проекту с точки зрения адекватности.
Старый 02.07.2014 12:44 Котяра вне форума
Котяра
 
Аватар для Котяра
Я немного не понял - про что статья? Про написание своего pureMVC или его использовании?
Старый 02.07.2014 12:53 Rembrant вне форума
Rembrant
 
Аватар для Rembrant
in4core, так и сделал. У самого периодически руки чесались

Котяра, и то, и другое
Старый 02.07.2014 12:59 Котяра вне форума
Котяра
 
Аватар для Котяра
Просто смущает использование pureMC™, хотя это совсем не он.
Статья должна называться "Самописный mvc фрэймворк (некоторые идеи из pureMVC)"
А то путает.
Старый 02.07.2014 13:24 Rembrant вне форума
Rembrant
 
Аватар для Rembrant
то есть фреймворк получился всё же mvc, а не puremvc.
В чём заключается "не он"?
Старый 02.07.2014 13:29 Котяра вне форума
Котяра
 
Аватар для Котяра
Ну потому, что это не он )
И совсем не "Программа на pureMVC".
Старый 02.07.2014 15:42 Котяра вне форума
Котяра
 
Аватар для Котяра
Ну ладно, пройдёмся, собственно, по сабжу.
Тут мы имеем классическое "ООП ради ООП".
Тема MVC на этом форуме - больная тема, вызывающая бурные обсуждения, но основной результат большинства всех обсуждений : "mvc-фрэймворк, как фрэймворк не особо то и нужен!"
Нужно просто строить своё конкретное приложение в этой парадигме. Да, некоторые классы (типа даты с баблингом или набор удачных комманд) могут гулять из проекта в проект; но строить проект вокруг "фрэймворка" - порочная практика.

Переходим к pureMVC и Вашей реализации.
Главный минус, я считаю, игнорирование нативного EventDispatcher и создание собственных нотификаторов. Ну и синглтоны M,V,C. Не надо их. Надо много моделей, много контроллеров, много вьюшек.
В разных иерархиях и детализациях.
Старый 02.07.2014 16:42 cleptoman вне форума
cleptoman
 
Аватар для cleptoman
Цитата:
В чём заключается "не он"?
в том, что Котяра имел ввиду одноименный готовый фреймворк "pureMVC" в то время как вы, как я догадываюсь, закладываете в это название некий подход "чистый MVC".
Старый 02.07.2014 17:41 Rembrant вне форума
Rembrant
 
Аватар для Rembrant
Цитата:
Тут мы имеем классическое "ООП ради ООП".
Тут вы совершенно правы, это была проба именно написания фреймворка ради него самого (в обычном случае я конечно бы использовал привычный EventDispatcher и доступ к данным попроще). В дальнейшем я могу забыть этот проект как страшный сон или же по истечении времени придётся вернуться к нему - как повезёт
Цитата:
в том, что Котяра имел ввиду одноименный готовый фреймворк "pureMVC" в то время как вы, как я догадываюсь, закладываете в это название некий подход "чистый MVC".
Теперь понятно. Я рассматривал puremvc как парадигму, а не готовый фреймворк, который не допускает изменений в своей структуре.
Старый 03.07.2014 12:42 Котяра вне форума
Котяра
 
Аватар для Котяра
EventDispatcher никак не ломает парадигму MVC, а очень даже помогает её реализовать.
Старый 03.07.2014 16:58 Rembrant вне форума
Rembrant
 
Аватар для Rembrant
Вставил вместо своего фреймворка скачанный из сети puremvc, подогнал свои классы. Заняло часа 2 (ожидал, что будет хуже). Правда, так и обошлось без EventDispatcher'ов - с ихними нотификациями получилось даже удобнее, чем с моим велосипедом.
Исходники перезалью, а старый код в статье пусть остаётся мне в назидание.
Старый 04.07.2014 11:44 Котяра вне форума
Котяра
 
Аватар для Котяра
Если вы не в курсе, кроме pureMVC для AS3 существует довольно много MVC фрэймворков.
Например Parsley, RobotLegs, Spring, Swiz, Mate - тысячи их.
А pureMVC вообще и для as3 в частности, далеко не самое удачное архитектурное решение.
Кроме того именно на классическом MVC свет клином не сошёлся - есть MVP, PM, MVVM и т.п.
(тут немного ссылок)
Старый 04.07.2014 12:58 Rembrant вне форума
Rembrant
 
Аватар для Rembrant
вкурсе. то что не самое удачное решение - видно хотя бы по количеству получившихся классов
однако захотелось добраться до истины. ещё бы в RobotLegs покопаться...
Старый 08.07.2014 04:15 Babylon вне форума
Babylon
 
Аватар для Babylon
Rembrant, не слушайте никого. Технологии для решений никогда не состоят из 5-ти строк. Но позволяют технологично решить задачу за 5-ть строк. Мне понравилось.
Старый 08.07.2014 11:15 Котяра вне форума
Котяра
 
Аватар для Котяра
Глупости какие. Если есть хорошее, удовлетворяющее текущим потребностям и расширяемое по необходимости решение в 5 строк - оно будет лучше решения в 150.
 
Последние записи от Rembrant

 


Часовой пояс GMT +4, время: 21:30.


Copyright © 1999-2008 Flasher.ru. All rights reserved.
Работает на vBulletin®. Copyright ©2000 - 2024, Jelsoft Enterprises Ltd. Перевод: zCarot
Администрация сайта не несёт ответственности за любую предоставленную посетителями информацию. Подробнее см. Правила.