Программа на pureMVC. Оно или нет?
Идея покопаться поподробнее в pureMVC была давно, потому как
В Интернете есть достаточное количество статей по фреймворку, из которых я почерпнул практически ничего, и пример с открытым кодом - галерея изображений, повергнувшая меня в уныние и ещё большее непонимание происходящего.
Итак, я поставил себе задачу написать программку на базе pureMVC - тоже галерею, но с минимумом подглядываний в прототип и с как можно меньшим количеством классов в принципе. Результат своих мучений представляю ниже на суд публики - постараюсь как можно подробнее расписать назначение получившихся классов.
Впрочем, даже после создания рабочего проекта я не могу сказать, что понял суть, а главное - назначение pureMVC, поэтому критика и швыряние помидоров всячески приветствуются.
И главный вопрос - а энто точно pureMVC получилось?
Итак, дерево проекта:
Часть 1.
Самописный фреймворк, который
Классы Controller, Model и View - основные одиночки, которые организовывают работу с Командами, Посредниками и Медиаторами соответственно. В каждом классе есть 2 словаря:
- _listClass - пары ключ-класс, которые по ключу позволяют работать со своими представителями. Все ключи лежат в классе Constants;
- _listCommand, _listProxy, _listMediator - пары ключ-экземпляр класса, которые в данный момент живые.
Весь открытый функционал - 3 функции работы из представителями:
- registerCommand, registerProxy, registerMediator соответственно - создаёт в словаре пару ключ-класс для дальнейшего вызова;
- executeCommand, executeProxy, executeMediator - создают экземпляр класса по ключу и выполняют его. Могут передавать данные. Так как фактически у меня все экземпляры представителей - одиночки, я не пересоздаю класс перед выполнением, если он есть. Возможно, это неправильно и требует переработки;
- removeCommand, removeProxy, removeMediator - удаление (как класса, так и его экземпляров). removeMediator дополнительно вызывает destroy() у экземпляров Медиаторов.
Дополнительно в Модели есть getProxy() для доступа к Представителям по ключу - для получения от них данных.
Естественно, всё это счастье держится на интерфейсах.
Controller.as:
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(); } }
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(); } }
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(); } }
BaseNotifier.as:
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); } } }
Facade.as:
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(); } } }
Собственно наша галерея - пакет app. Базируется на частном Фасаде AppFacade, который наследуется от Facade. Для старта нужно всего лишь запустить его в Main.as:
AppFacade на старте регистрирует и выполняет первую команду. Кроме того он замещает абстрактную функцию - обработчик уведомлений handleNotify. Чтобы никого не испугать, сначала я оставлю её пустой.
AppFacade.as:
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; } }
LoadFilesCommand.as:
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.as:
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"); } } }
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:
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:
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 } ); } } }
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; }
LoadPreviewCommand.as:
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 } ); } } }
Кроме того, чтобы не тянуть соплёй индекс через все классы, я оставляю его здесь и даю доступ на чтение. Вот здесь и понадобится getProxy() из Model.
LoadPreviewProxy.as:
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"); } } }
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:
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 } ); } } }
Медиатор построен так, что по вызову execute() он мог создать весь свой вид (кнопки и фон) или только обновить изображение (если кнопки и фон уже есть). Таким образом, при кликах на Вперёд-Назад, его не нужно пересоздавать.
По нажатию на кнопки он вызывает уже использованное ранее уведомление к Фасаду NOTIFY_TILE_CLICK с новым индексом. По нажатию на фон - новое уведомление NOTIFY_CLOSE_PREVIEW.
PreviewMediator.as:
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); } } }
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:
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, а старый код в статье пусть остаётся мне в назидание.
Всего комментариев 20
Комментарии
01.07.2014 13:40 | |
Цитата:
Но передавать стейдж куда то там.... это не МВС.
|
01.07.2014 14:01 | |
Есть "теория шести рукопожатий". Я её стараюсь придерживаться и тем самым проблема доступности stage и подобных глобальных объектов немного отступает. По сути не нужно передавать ссылку на него в каждый объект, нужно лишь добраться до объекта, имеющего на него ссылку.
|
01.07.2014 14:46 | |
dimarik, ну доступ к объекту со stage'м - Фасада, есть у всех. Только как я понял из первого комментария, это будет не канонично по puremvc.
|
01.07.2014 17:20 | |
Честно говоря, я не понимаю, зачем нужен pureMVC. Да, я начинал сразу с Flex и, соответственно, c AS3.
Зачем усложнять себе жизнь и отказываться от MXML, Data Binding? Паттерн Presentation Model очень хорошо сочетается с каким-нибудь Dependency Injection (сам я пользуюсь Parsley). Модель отправляет событие, фреймворк запускает команду по событию, передает ответ в модель. |
02.07.2014 12:44 | |
Я немного не понял - про что статья? Про написание своего pureMVC или его использовании?
|
02.07.2014 12:53 | |
in4core, так и сделал. У самого периодически руки чесались
Котяра, и то, и другое |
02.07.2014 12:59 | |
Просто смущает использование pureMC™, хотя это совсем не он.
Статья должна называться "Самописный mvc фрэймворк (некоторые идеи из pureMVC)" А то путает. |
02.07.2014 13:24 | |
то есть фреймворк получился всё же mvc, а не puremvc.
В чём заключается "не он"? |
02.07.2014 13:29 | |
Ну потому, что это не он )
И совсем не "Программа на pureMVC". |
03.07.2014 12:42 | |
EventDispatcher никак не ломает парадигму MVC, а очень даже помогает её реализовать.
|
04.07.2014 11:44 | |
Если вы не в курсе, кроме pureMVC для AS3 существует довольно много MVC фрэймворков.
Например Parsley, RobotLegs, Spring, Swiz, Mate - тысячи их. А pureMVC вообще и для as3 в частности, далеко не самое удачное архитектурное решение. Кроме того именно на классическом MVC свет клином не сошёлся - есть MVP, PM, MVVM и т.п. (тут немного ссылок) |
04.07.2014 12:58 | |
вкурсе. то что не самое удачное решение - видно хотя бы по количеству получившихся классов
однако захотелось добраться до истины. ещё бы в RobotLegs покопаться... |
08.07.2014 04:15 | |
Rembrant, не слушайте никого. Технологии для решений никогда не состоят из 5-ти строк. Но позволяют технологично решить задачу за 5-ть строк. Мне понравилось.
|
08.07.2014 11:15 | |
Глупости какие. Если есть хорошее, удовлетворяющее текущим потребностям и расширяемое по необходимости решение в 5 строк - оно будет лучше решения в 150.
|
Последние записи от Rembrant
- ООП ради ООП ч. 2. Мучаем robotlegs (21.07.2014)
- Программа на pureMVC. Оно или нет? (30.06.2014)