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

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

Рейтинг: 4.79. Голосов: 19.

MVC, часть 1: про дубовый стол и сиськи

Запись от Psycho Tiger размещена 30.10.2010 в 02:36
Обновил(-а) Psycho Tiger 14.12.2010 в 18:10

[Что-то очень глючил редактор, где то возможны нестыковки - текст порой просто удалялся. Замечания пишите в комменты. Спасибо]

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

Думайте это о Вас? Обо мне? Пфф... Это Вам не сага о Горце. Тут придется включить свою голову. Ну или хотя бы сделать вид. Как бы то ни было - купите виски и откиньтесь на спинку кожаного кресла, положив свои ноги в ботинках из крокодиловой кожи на стол из столетнего дуба. Да-да, вы не ослышались. MVC - это действительно круто. Соплякам тут не место )

Мне всегда вот в научных статьях, учебниках по квантовой физике и статьях на википедии не нравилось одно - сухость материала. Ну пришел Шредингер, сказал что нашел универсальное уравнение в материи в целом - и понеслись иероглифы. Ну вот как это следует вообще понимать? Тут тоньше надо. Не спугнуть читателя, объяснять всё простым языком... Тут тренд Дисковери уловил, конечно. Поэтому давайте-ка я расскажу вам о трёх буквах.

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

Начнем, пожалуй, с истории среднестатистического флеш-разработчика. Речь, конечно же, пойдёт о человеке растущим на флеше с 0, а не матёрым Java`ером, который решил переквалифицироваться.

Сперва разработчик начинает писать свой код в кадрах/в одном классе. Он не понимает, зачем создавать другие классы, ведь всё можно написать здесь - и оформить функцией - то есть замыканием. Некоторые о приёме замыкания не знают - и слава богу - и пишут в методах.
Наступает момент, когда кода становится слишком много и под давлением общественности человек начинает делать попытки создавать другие классы. И тут же чувствует себя скованным - как же получить глобальную переменную... В следствии чего в конструктор пихает всё что только может. А ещё лучше - ссылку на класс, который его и создал. В итоге Main порождает пятерку других классов. И все знают всё о Main.
Потом человек понимает, что так передавать - крайне неэффективно. И начинает использовать синглтон. Ну как синглтон... Он думает, что это синглтон. На самом деле - просто набор статический методов и переменных. Ну должен же быть глобальный контроль!

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

И вот только потом, через много времени он чувствует себя готовым. Готовым к большим открытиям и большим наградам. И виднеются на горизонте три большие, заглавные буквы - M, символизирующие Молодец разработчику, V, символизирующее пятерку за архитектуру и C - символизирующее Сиськи, которых порой так не хватает рядом во время длительной разработки. Ну, это для меня. MVC - у каждого своё. Вроде бы очевидно, что от MVC со временем не так то просто отказаться.


...Затянулось начало. Перейдём к делу.
Чтобы было сразу понятней - сразу же введем пример. Предположим, у нас есть герой у которого есть 3 вида оружия. Вид игры - сверху.
Рассмотрим отдельно все части:
M - Model. Модель. Хранит данные, которые нужны триаде (MVC). Это - координаты игрока в мире, текущие оружие в руках, количество здоровья, количество боеприпасов. Остальные данные - например количество крови хлыщущее из его вены в модели не хранятся.

V - View. Вьюшка. Отображалка. Занимается всем тем, что видит и слышит пользователь. Хранит в себе как раз количество крови, которое из игрока хлыщет, само изображение игрока, создаёт звуки выстрелов... Ну вы поняли. Это всё, что вообще есть на экране.

C - Controller. Контроллер. Он занимается логикой всей триады. Именно он обрабатывает нажатие клавиш и меняет данные в модели, записывая туда новые координаты игрока. Именно он разрешает игроку стрелять и меняет у него оружие.

Всё понятно? Не пытайтесь проводить грань и понять взаимодействие между ними - просто определитесь, кто что делает.

Сейчас я дам вам картинку, нагло взятую где то из интернета.


Какая прелесть, правда? Давайте разберемся. Сплошными линиями показаны жесткие связи между трио - то есть у контроллера есть прямая ссылка на модель и на вьюшку, а у вьюшки на модель. Штрихованными же показано кто кого слушает - контроллер слушает вьюшку, а вьюшка модель. Что такое "слушает" - ниже, на примере каждого.

Пример с игрой и человечком сверху был удачным для объяснения идеи текстом, но мне кажется для практической реализации нужно взять пример попроще. Давайте сделаем совсем элементарный пример: несколько квадратиков, все разных цветов. При этом есть одна стрелочка, которая указывает на один из квадратов. По клику стрелочка указывает на случайный квадратик, а каждые 3 секунды цвета у квадратов меняются. Это игра будет для детей дошкольного возраста: они должны будут произносить вслух цвет, на который указывает стрелочка, а воспитатель скажет, правильно или нет.
Итак. Сперва мы должны создать Главный контроллер - он всегда самый главный в любом приложении. У нас будет всего один контроллер, одна модель и одна вьюшка - поэтому контроллер главный, и по совместительству единственный. Контроллеру нужно знать, куда добавлять всю прелестную графику? Нужно. Поэтому ему нужен иметь какую-то точку привязки для графики - DisplayObjectContainer. Где её взять? Это - ваш класс Main, который базовый. Как создать главный контроллер? В Main, в конструкторе или в обработчике события Event.ADDED_TO_STAGE нужно написать примерно следующее:
Код AS3:
new BaseController(this);
Мы просто создали этот контроллер и передали ссылку на себя - то есть на то место, где будет скапливаться вся графика. Нам даже не нужно сохранять ссылку на главный контроллер!
Код класса BaseController:
Код AS3:
package
{
	import flash.display.DisplayObjectContainer;
 
	public class BaseController
	{
		private var _host:DisplayObjectContainer;
		private var _model:Model;
		private var _view:View;
 
		public function BaseController(host:DisplayObjectContainer)
		{
			super();
			_host = host;
			_model = new Model();
			_view = new View(_model);
 
		}
 
	}
 
}
Что мы сделали? Мы просто в конструкторе сразу же создали и модель, и вьюшку.
Давайте напишем модель. Сперва нужно понять, что должно быть в модели. В модели должны быть данные, которые нужны всей триаде. Это - цвета всех квадратов и номер квадрата, на который указывает стрелочка. "А как же графика для стрелочки, размеры квадрата, расстояние между ними и всё остальное?" - воскликнет читатель. Нет, эти данные не должны хранится в модели. Вся графика, все звуки и всё остальное - это забота вьюшки, в этом и прелесть: сегодня у меня на экране человек, а завтра киборг. При этом я переписываю только отображение, не трогая контроллер и модель, тем самым не "ломая" логику. Размер прямоугольников - не нужен в контексте этой задачи - нам плевать на форму, хоть квадратная, хоть скругленная. Нам так же плевать на позиции - хоть синусоидально, хоть по прямой. На логику это не влияет. Нам просто нужно выводить на экран какие-то цветные элементы и помечать их. Отсюда всплывает очередная прелесть - мы можем не сильно заботится о том, что творится на экране, оставив это на "потом". Будь другой контекст - например, если бы нужно было отгадать размер в пикселах этой фигуры - то эти данные попали бы в модель. Аналогично и с формой, и с расположением. Проще говоря, в модели хранятся значимые данные в данной задаче. А на фоне гусей или океана это происходит - программу не колышет.

Итак, нам нужно хранить в модели массив цветов и номер прямоугольника, на который указывает стрелка. При этом при изменении информации в модели неплохо было бы заявить всем желающим, что данные изменились - неплохо бы их обновить. Это означает, что модель должна испустить событие о том, что она изменилась. Делается это GoF паттерном Observer, но не пугайтесь: обычная событийная флешевая событийная модель реализует именно его, поэтому нам совсем не стоит об этом беспокоится. При обновлении информации есть 2 подхода: push и pull. При обновлении информации мы генерируем событие о том, что что-то изменилось. Представим, что это событие кто-то поймал. В событии может содержаться вся информация о том, что же изменилось - такой подход называется push - мы проталкиваем с событием всё что нужно обновить. Другой подход - pull - значит "тяни". В таком случае события носят нотификационный характер - что-то вроде "Эй парень, число народу в игре обновилось.". А сколько теперь онлайн - это событие не скажет. И в таком случае эти данные приходится "тянуть" с модели явно. Я использую оба подхода, оба подхода хороши. Но конкретно сейчас я ограничусь pull-подходом. Кстати, событийное поведение добавляет ещё одно удобство, одно из главных - если у нас в 5 разных местах показывается, например, баланс игрока - то при изменении баланса в одном месте он должен поменяться во всех.
Но тут всплывает проблема: поменяв номер прямоугольника, на который указывает стрелочка вызовется сеттер, который испустит событие об изменении. Но в случае с массивом всё не так: чтобы вызвать сеттер у массива нужно будет переопределить на него ссылку, что недопустимо: нам нужно просто менять элементы массива, но не ссылку на него. Решений у проблемы несколько. Например, самый извращенный - это унаследовать модель от Proxy, определив там поведение для [], попутно реализовав ей IEventDispatcher. Можно, например, сделать метод setElement, в теле которых и генерировать событие об изменении. Можно в модели добавить метод "сделай событие пожалуйста, что ты изменилась" и дёргать его. Но немного подумав вспоминаем, что модель мы меняем в контроллере. А в контроллере мы имеем полный контроль над вьюшкой. Поэтому в этом случае на мой взгляд самым лучшим решением будет явно вызвать метод у вьюшки после того как закончится изменение модели.
Код Model:
Код AS3:
package  
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
 
	[Event(name="change", type="flash.events.Event")]
	public class Model extends EventDispatcher implements IReadableModel
	{
 
		private var _pointer:int = 0;
		public var colors:Array = [];
		public function Model() 
		{
			super();
		}
 
		public function get pointer():int { return _pointer; }
 
		public function set pointer(value:int):void 
		{
			if (_pointer == value) return;
			_pointer = value;
			super.dispatchEvent(new Event(Event.CHANGE));
		}
 
	}
 
}
Теперь сделаем вот такой вот финт в голове: абстрагируемся от трио и посмотрим только на модель. Просто храним данные, по возможности говорим всем желающим событием о том, что данные изменились. Ничего сложного, ведь так?

Итак, теперь View. Как мы поняли ранее, у View есть прямая ссылка на Model, в итоге нам нужно всего лишь визуализировать эти данные.
Код AS3:
package  
{
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.events.Event;
 
	public class View extends Sprite 
	{
		private var _model:Model;
		private var _squares:Array;
		private var _pointer:Shape;
 
		public function View(model:Model) {
			super();
			_model = model;
			 //здесь будем хранить ссылки на созданные графические объекты
			_squares = [];
			//это типа стрелочка
			_pointer = new Shape(); 
			_pointer.graphics.beginFill(0x00FF00);
			//круглая такая стрелочка
			_pointer.graphics.drawCircle(0, 0, 30); 
			_pointer.y = 100;
			_pointer.graphics.endFill();
 
			super.addChild(_pointer);
 
			//вызываем обработчик как будто-бы модель изменилась. Это нужно чтобы указатель
			//сразу встал на нужный квадрат
			onPointerChange(null);
 
			 //подписались на изменение модели
			_model.addEventListener(Event.CHANGE, onPointerChange);
		}
 
 
		public function updateSquares():void {
			//поменялись цвета - удаляем все старые прямоугольники
			var i:int = _squares.length;
			while (i--) super.removeChild(_squares.pop());
			//рисуем новые
			i = _model.colors.length;
			while (i--) {
				var shape:Shape = new Shape();
				shape.graphics.beginFill(_model.colors[i]);
				shape.graphics.drawRect(0, 0, 50, 50);
				shape.graphics.endFill();
				shape.x = 100 * i;
				_squares.push(shape);
				super.addChild(shape);
			}
		}
 
		private function onPointerChange(event:Event):void {
			_pointer.x = 100 * _model.pointer;
		}
 
	}
 
}
Мы рисуем квадраты - по цветам из массива, который хранится в модели и стрелочку, которая после конца кризиса набрала килограммов и стала круглой, заставляя её указывать на какой-то из квадратов. Какой - всё так же заявлено в модели.
Абстрагируемся. Нам сообщают событиями о том, что модель изменилась. Если мы получили такое сообщение - меняем изображение, чтобы быть актуальным каким-то данным, которые есть в модели. Попутно имеем паблик метод updateSquares, который будет вызван кем-то сверху, когда нужно обновить квадраты. Если не думать о трио в целом, а только об одном элементе - всё очень просто.
Резюме. Мы имеем что-то, что предоставляет нам данные и говорит, когда данные поменялись. На основе этого мы строим изображение. Всё просто? Отлично. Теперь связываем в голове модель и вьюшку. Связываем, думаем.

Готовы продолжать? Теперь контроллер.
Задача контроллера - менять модель. Просто писать в неё данные. Абстрагируемся вновь. Мы просто делаем какую-то логику, и пишем её в объект, не задумываясь о том что происходит дальше. Но ещё нам нужно вот что сделать в контроллере: добавить вьюшку в дисплай лист - т.е. просто сделать addChild к тому, что мы передали в конструктор главному контроллеру. По условию задачи менять каждые 3 секунды цвета. Что-ж, ничего сложного! Делаем:
Код AS3:
package  
{
	import flash.display.DisplayObjectContainer;
	import flash.events.TimerEvent;
	import flash.utils.Timer;
 
	public class BaseController 
	{
		private var _host:DisplayObjectContainer;
		private var _model:Model;
		private var _view:View;
 
		public function BaseController(host:DisplayObjectContainer) 
		{
			super();
			_host = host;
			_model = new Model();
			_view = new View(_model);
 
			//создали таймер и запустили
			var _timer:Timer = new Timer(3000);
			_timer.addEventListener(TimerEvent.TIMER, onTimer);
			_timer.start();
 
			//сразу же вызвали обработчик, чтобы сразу при запуске забить массив
			//случайными квадратами
			onTimer(null);
 
			//добавили в дисплай лист - чтобы мы смогли увидеть работу вьюшки
			host.addChild(_view);
		}
 
		private function onTimer(event:TimerEvent):void 
		{
			//забиваем модель случайными цветами
			var i:int = 5;
			while (i--) {
				_model.colors[i] = int(Math.random() * 0xFFFFFF);
			}
 
			//говорим, что мы изменили цвета
			//это как раз тот момент из за невозможности вызова геттера
			_view.updateSquares();
		}
 
	}
 
}
И снова пытаемся представить контроллер как нечто "не связанное" с моделью и вьюшкой логически. Да-да, мы уже абстрагировались ещё перед написанием, но давайте не ленится. Если у вас получается, то он снова кажется простым - всего лишь меняем массив в каком-то объекте каждые 3 секунды. После этого пытаемся представить целостную картину, но это не всё. Мы не сделали интерактивность: стрелка (в нашем случае это круг) всегда будет указывать в одно место, а нам же необходимо чтобы стрелочка меняла положение на случайное по клику. Напоминаю, что это положение находится в модели.
Как это сделать? Наверное вам в голову лезет мысль - в контроллере отлавливать клик и менять модель. Нет, это не совсем правильно: это лепка программы "под конкретный вариант", а это как раз то, против чего создано MVC. Пользователь с чем взаимодействует? Только с вьюшкой. Он не может кликнуть по модели или по контроллеру - он кликает по экрану, что по сути является вьюшкой. Значит клик должна обрабатывать вьюшка. При этом это слишком связано, что именно по клику: сегодня по клику, завтра по нажатию клавиши, а послезавтра вообще по голосовой команде. Вьюшка должна сообщить о том, что надо бы поменять стрелочку - а уж отчего вьюшка так решила знает только она сама. В итоге во View дописываем функционал, что по клику диспатчим какое-то событие, которое слушает контроллер. Я опять буду использовать событие Event.CHANGE - поначалу могут возникнуть сложности - как же так, мы ведь его уже используем в модели - но это здорово развивает абстракцию. В реальных проектах типов событий у меня очень часто больше 30-40, тут, конечно, одним Event.CHANGE не обойтись и я делаю свой класс, расширяющий Event. Однако, какой случай - такой и инструмент, поэтому здесь Event.CHANGE.
Код AS3:
package  
{
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
 
	[Event(name="change", type="flash.events.Event")]
	public class View extends Sprite 
	{
		private var _model:Model;
		private var _squares:Array;
		private var _pointer:Shape;
 
		public function View(model:Model) {
			super();
			_model = model;
			 //здесь будем хранить ссылки на созданные графические объекты
			_squares = [];
			//это типа стрелочка
			_pointer = new Shape(); 
			_pointer.graphics.beginFill(0x00FF00);
			//круглая такая стрелочка
			_pointer.graphics.drawCircle(0, 0, 30); 
			_pointer.y = 100;
			_pointer.graphics.endFill();
 
			super.addChild(_pointer);
 
			//вызываем обработчик как будто-бы модель изменилась. Это нужно чтобы указатель
			//сразу встал на нужный квадрат
			onPointerChange(null);
 
			 //подписались на изменение модели
			_model.addEventListener(Event.CHANGE, onPointerChange);
 
			//нам должен быть доступен stage
			super.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
		}
 
		private function onAddedToStage(event:Event):void 
		{
			super.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
			//подписались на заветный click
			super.stage.addEventListener(MouseEvent.CLICK, onClick);
		}
 
		private function onClick(event:MouseEvent):void 
		{
			//и выпустили событие
			super.dispatchEvent(new Event(Event.CHANGE));
		}
 
		public function updateSquares():void {
			//поменялись цвета - удаляем все старые прямоугольники
			var i:int = _squares.length;
			while (i--) super.removeChild(_squares.pop());
			//рисуем новые
			i = _model.colors.length;
			while (i--) {
				var shape:Shape = new Shape();
				shape.graphics.beginFill(_model.colors[i]);
				shape.graphics.drawRect(0, 0, 50, 50);
				shape.graphics.endFill();
				shape.x = 100 * i;
				_squares.push(shape);
				super.addChild(shape);
			}
		}
 
		private function onPointerChange(event:Event):void {
			_pointer.x = 100 * _model.pointer;
		}
 
	}
 
}
Это полный код View. Просто дописали, что по клику происходит испускание события Event.CHANGE.
Теперь немного перепишем контроллер:
Код AS3:
package  
{
	import flash.display.DisplayObjectContainer;
	import flash.events.Event;
	import flash.events.TimerEvent;
	import flash.utils.Timer;
 
	public class BaseController 
	{
		private var _host:DisplayObjectContainer;
		private var _model:Model;
		private var _view:View;
 
		public function BaseController(host:DisplayObjectContainer) 
		{
			super();
			_host = host;
			_model = new Model();
			_view = new View(_model);
 
			//создали таймер и запустили
			var _timer:Timer = new Timer(3000);
			_timer.addEventListener(TimerEvent.TIMER, onTimer);
			_timer.start();
 
			//сразу же вызвали обработчик, чтобы сразу при запуске забить массив
			//случайными квадратами
			onTimer(null);
 
			//добавили в дисплай лист - чтобы мы смогли увидеть работу вьюшки
			host.addChild(_view);
 
			//подписались на событие, по которому нужно изменить модель
			_view.addEventListener(Event.CHANGE, onViewChange);
		}
 
		private function onViewChange(event:Event):void 
		{
			//просто случайная позиция для стрелочки
			_model.pointer = Math.random() * 5;
		}
 
		private function onTimer(event:TimerEvent):void 
		{
			//забиваем модель случайными цветами
			var i:int = 5;
			while (i--) {
				_model.colors[i] = int(Math.random() * 0xFFFFFF);
			}
 
			//говорим, что мы изменили цвета
			//это как раз тот момент из за невозможности вызова геттера
			_view.updateSquares();
		}
 
	}
 
}
Тут тоже всё банально и просто - как только поймали событие Event.CHANGE от View - сразу же поменяли модель, которая в свою очередь побудила измениться отображению. Такой круговорот получится - View - Controller - Model - View. Забавно, правда?

Теперь пролистните вверх и посмотрите снова на картинку и вновь перечитайте часть, где я рассказывал про связи между M, V и C (Model, View и Controller). Теперь всё куда понятней, не правда ли?

Но и это не всё.
View не может изменять Model, как вы уже поняли, а у нас может. Как это исправить? Вью нужно передавать не саму модель, а только интерфейс, в которым "порезаны" все set методы. Например, в нашем случае такой:
Код AS3:
package  
{	
	public interface IReadableModel 
	{
		function get pointer():int; 
		function get colors:Array;
	}
 
}
(необходимо будет добавить геттер colors в Model)
Однако, мы всё равно можем изменить элемента массива colors. Можно, конечно, придумать метод getElementAt, который бы лез в массив и возвращал элемент на заданной позиции, тем самым запретив изменять элементы массива... однако запрет на изменение модели не имеет другого характера, кроме как "защиты от дурака/запарки", чтобы случайно вы сами не поменяли модель. Мне не было бы стыдно оставить интерфейс в виде, который я привёл чуть выше - если вы не доверяете себе или людям, с которыми работаете - пишите метод. Это - инструмент, и он должен подстраиваться под вас, а не вы под него. Не стесняйтесь смотреть на картинку - она сильно помогает первое время. Если она даже сейчас кажется вам сложной - не смотрите на неё, а читайте текст и рисуйте эту картинку у себя на бумажке. Не ленитесь, это действительно помогает.

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

Вторая часть.
Всего комментариев 231

Комментарии

Старый 29.11.2011 17:58 incvizitor вне форума
incvizitor
 
Аватар для incvizitor
fish_r, для меня вообще нету такого понятия как "центр тяжести". все 3 части приложения можно в любой момент заменить. Даже в ран тайме.
Старый 30.11.2011 01:36 fish_r вне форума
fish_r
 
Аватар для fish_r
Цитата:
...для меня вообще нету такого понятия как "центр тяжести".
это не понятие, это образное выражение...
Старый 30.11.2011 03:48 imena вне форума
imena
Я прошу прощения, но снова задам нубовские вопросы.

Почитал коменты и в одном из них прочел следующее(не понял где тут кнопочка "Цитата")... в общем... контроллер реагирует на сигналы/события и поэтому мы можем завтра поменять вьюху на другую с подобными сигналами и все будет тип-топ. Я в самом начале сделал себе контроллер в котором были различные события и в самой вьюхе создавая какой-нибудь объект-кнопку я передавал в конструкторе то событие(которое у меня в контроллере зарегистрировано) которое должна была генерить эта кнопка когда на нее нажимаем. А в самом контроллере сразу писал обработчик. Т.е., я мог создать любую кнопку и не париться по поводу того чтобы переписывать контроллер, т.к. в эту кнопку я передавал лишь те события которые были у контроллера. И мне не надо было париться по поводу того чтобы создавая новую кнопку писать каждый раз специально для нее реакцию... сегодя эта кнопка делает одно, завтра другое. Получив событие, контроллер(мой) определял что надо дальше делать.. либо менять модель, либо посылать указания вьюхе. И контроллер у меня расширял EventDispatcher. Но прочитав статьи Тайгера... получается что я все сделал неправильно.

private function clickListener(e:MouseEvent):void {

this.target.dispatchEvent(new Event(event));
}

Вопрос - такой подход в корне неправильный?

Теперь вопрос по моделе. Я тут поразмышлял, положение в пространстве нашего объекта тоже не может храниться в моделе, т.к. мм ну допустим у нас идет чел по широкой улице и начальная точка у него у правого нижнего края улицы. Завтра мы меняем улицу на узкое ущелье, запускаем приложение и наш герой уже находится внутри скалы, т.к. стартовая точка была у нижнего правого края ШИРОКОЙ улицы. И нам приходится менять саму модель. Или, на примере объекта "собачка". В моделе, как понял должно храниться только то что мы добавили нашу собачку, а также порода, возраст, уровень сытости... но где эта собачка сейчас - Point(100, 100) или Point(0, 0) модель никак не волнует. Т.е. мой вопрос - координаты мы тоже не должны хранить в моделе? Но где их тогда хранить?

Еще вопрос. Как я понял(если я правильно понял) модель - она что то там описывает. Допустим у нас зоопарк(не игра) просто зоопарк. Не учитываем пока забор, вольеры, пруды для уток. Просто зверушки.
Я так понимаю, мы должны(раз наша модель описывает зоопарк) объявить изначально всех зверушек... и присвоить им начальное значение 0. Затем, по мере наполнения зоопарка(нажатие кнопки купить верблюда), контроллер говорит моделе увеличить число верблюдов. Т.е. мой вопрос в том что, нам надо объявлять в самом начале чего и сколько у нас есть? Т.е., если модель сама по себе будет пустой, то в наш зоопарк контроллер может ведь и автосалон запихать - addObject("бла-бла-бла"); Или я неприавльно мыслю?

А если правильно, то в чем суть модели? Она же тупо является ВРЕМЕННЫМ контейнером для хранения, в котором к тому же нет данных относительно параметров объекта, его положения и т.д. и т.п. Как тогда все эти данные сохранять потом? Модель сохраняет одно, вьюха или контроллер данные о ширине и высоте объекта. Я просто никак не могу понять почему это все нельзя собрать в одном месте, а не дергать информацию оттуда, отсюда. Или может я вообще не в ту сторону плюю.

З.Ы. Прошу еще раз прощения за свои нубовские вопросы... просто все себе расписал.. в этом месяце прочитать мука, в след...то-то... но нарвался на MVC и все полетело к чертям не знаю с какой стороны подходить
Старый 30.11.2011 11:01 incvizitor вне форума
incvizitor
 
Аватар для incvizitor
Ставте пожалуйста номерки рядом с вопросами.
1 - На счет того, что же дисатчит въюха мнения как раз и разделились. Вы бы написали что есть "target", а то телепаты временно в спячке. И код тоже не мешало бы оформить.
2 - Не очень понял про ущелье, но вот допустим у нас игра 2д. Улица/ущелье не имеет поворотов (для простоты примера). Тогда модель знает координаты левой и правой стороны улицы. Она не пустит объект ни левее ни правее. Вообще в таких случаях есть файл в котором прописано куда что ставить, или же есть алгоритм, который раставляет объекты в зависимости от обстоятельств.
3 -
Код AS3:
public function addObject(data:AnimalData):void{
    //....
}
Автосалон сюда никак не запихнуть, ну только если он не экстэндит AnimalData =)

4 - Еще раз: Положения в пространстве хранятся в модели. Контроллер их изменяет, модель оповещает об этом представление.
Старый 30.11.2011 20:40 imena вне форума
imena
А кто делает проверку данных? Например, в том же примере с зоопарком. Контроллер проверяет какой объект пытаемся добавить в модель или модель проводит проверку типов и генерит исключения в случае чего?

З.Ы. target - это был мой контроллер, в котором были прописаны события типа "ADD VERBLUD", "REMOVE_VERBLUD", etc.
Старый 01.12.2011 02:32 incvizitor вне форума
incvizitor
 
Аватар для incvizitor
Модель по любому выкинет эксепшн если типы не совпадут. Но вообще если ситуация когда кто то пихает, не то что нужно возможна, то можно:
1 - в контроллере проверять что мы добавляем.
2 - добавлять в модель, а она уже потом решит добавить в автосалон или в зоопарк.

Цитата:
З.Ы. target - это был мой контроллер, в котором были прописаны события типа "ADD VERBLUD", "REMOVE_VERBLUD", etc.
То есть въюшка вызывает у контроллера метод dispatchEvent? Если да - тогда такой подход не правильный. У въюшки НЕ ДОЛЖНО быть ссылки на контроллер вообще.
Старый 01.12.2011 04:01 imena вне форума
imena
тогда, получается что ..
1 - если у нас есть котроллер, то мы должны подбирать под него вьюшку или писать новую но все равно под контроллер?
2 - если у нас нет контроллера, тогда надо писать контроллер(если у нас вообще нет никаких контроллеров) под вьюшку?
Старый 01.12.2011 05:13 incvizitor вне форума
incvizitor
 
Аватар для incvizitor
1 - Откуда такой вывод? Въюшка должна оповещать контроллер, она должна иметь набор событий(сигналов) которые оповещают контроллер. Контроллеров которые слушают эти события может быть хоть милион видов. Причем въюшка может "уметь" оповещать контроллер только о части событий, которые контроллер слушает и наоборот (хотя второе не желательно).
2 - Под интерфейс въюшки.
Старый 01.12.2011 05:52 imena вне форума
imena
Именно под интерфейс? Это для того чтобы контроллер не мог изменить вьюшку? Если да, тогда я опять запутался. На одном из сайтов была представлена диаграмма, если не трудно, гляньте, пожалуйста и скажите правильно там или нет. Просто, там в диаграмме контроллер получает данные, проверяет их, отсылает модели, модель проводит вычисления и отсылает все это !!! контроллеру!!! а тот отсылает вьшке.

Сайт: http://chtivo.webhost.ru/articles/mvc.php
Старый 01.12.2011 06:22 imena вне форума
imena
кстати, если следовать диаграмме с сайта, который я привел выше... вьюшка никак не может изменить модель что очень даже хорошо(как мне кажется).
Старый 01.12.2011 06:44 imena вне форума
imena
Мне кажется, возлагать функцию проверки на Модель не совсем правильно. Теряется смысл в Контроллере.

Приведу пример. Раньше, король, перед тем как съесть что-то приказывал специальным людям пробовать блюда. И если с ними ничего не случалось...ел сам. Т.е., кухня - View, спец. челы - Controller, король - Model. Король съел - модель изменилась(сытость +1). Если контроллер не будет проводить проверку, тогда блюда могут с кухни передаваться королю теми же поворятами и смысл в спец. челах пропадает.

Вот такое мое мнение. ))
Старый 01.12.2011 12:09 incvizitor вне форума
incvizitor
 
Аватар для incvizitor
Цитата:
Именно под интерфейс? Это для того чтобы контроллер не мог изменить вьюшку? Если да, тогда я опять запутался.
Нет, для того что бы въюшку можно было заменить в любой момент. Неизменным остается лишь интерфейс.

Въюшка ничего не должна менять в модели. А оповещение об изменении приходит во въюшку из модели. Посмотрите диаграму в статье.
Старый 01.12.2011 14:00 imena вне форума
imena
Вот блин, то ли я не так смотрю, то ли диаграмма не та.... оповещение от модели приходит в контроллер
Старый 01.12.2011 16:17 incvizitor вне форума
incvizitor
 
Аватар для incvizitor
Диограмма не та, я имею ввиду в этой статье, а не то в той что по Вашей ссылке =)
Старый 04.12.2011 04:17 imena вне форума
imena
))) елки-палки... кто прав, а кто нет? Т.е., не важно кто прав, важно как на самом деле правильно
Вот нашел сегодня еще одну статью....
http://easyflash.org/flashlearn/flas...rollermvc.html

Хотелось бы услышать ваше мнение
Старый 04.12.2011 05:29 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
Прокрутил. Увидел это:
Цитата:
Model

Модель должна содержать ссылку на представление.
И это:
Цитата:
View

Представление должно содержать ссылки на модель и на контроллер .
То, ради чего задумывалось MVC свели на нет. Не читайте эту статью. Облейте её дорожающим бензином и подожгите: дорого, но надо.
Старый 04.12.2011 06:48 imena вне форума
imena
Прошу прощения что спрашиваю про каждую мелочь, но для меня сейчас, пока я полностью не понял MVC, каждая мелочь важна. Просто, в сети столько инфы, в том числе и противоречивой.

Вопрос 1: У нас проигрыватель. Кнопка "+" и кнопка "-" для регулировки звука. Кто решает на сколько увеличивать уровень звука - на 1 или 0.5 условных единиц? Контроллер или модель?

Вопрос 2: Допустим, у нас есть главный герой. Мы кликаем на каком-нибудь месте игрового поля. Вьюшка диспатчит событие. Контроллер отлавливает и.... что происходит дальше?

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

Правильно? Или я ошибаюсь?

Вопрос 3: У нас анимация. Никуда не перемещаемся, в просто... ну пусть будет мельница. Вот, случилось у нас событие ENTER_FRAME. Вьюшка должна сообщить об этом контроллеру? Если да, то...кто должен поменять текущий кадр? Контроллер? Явно не модель, т.к. модель не отвечает за текущий кадр. Или контроллер вызывает функцию View.update(deltaTime), в которой идет вычисление следующего кадра? А в контроллере идет лишь вычисление deltaTime?
Обновил(-а) imena 04.12.2011 в 06:51
Старый 04.12.2011 15:05 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
Цитата:
Вопрос 1: У нас проигрыватель. Кнопка "+" и кнопка "-" для регулировки звука. Кто решает на сколько увеличивать уровень звука - на 1 или 0.5 условных единиц? Контроллер или модель?
Звук это приблуда вью. Скорее, НА сколько решает какой-то конфиг (возможно, контроллер) и кладёт в модель. А меняет вью, беря данные из модели.
Цитата:
Вопрос 2: Допустим, у нас есть главный герой. Мы кликаем на каком-нибудь месте игрового поля. Вьюшка диспатчит событие. Контроллер отлавливает и.... что происходит дальше?
Заставляет героя идти к месту. Например, говорит модели - перемести героя в точку x1,y1. И модель начинает менять свои координаты, а вьюшка их подсасывает. Или вьюшка полностью сама двигает героя и просто говорит, когда герой пошел, а когда дошел.
Ваш вариант тоже правильный. Как хотите, в общем.
Цитата:
Вопрос 3: У нас анимация. Никуда не перемещаемся, в просто... ну пусть будет мельница. Вот, случилось у нас событие ENTER_FRAME. Вьюшка должна сообщить об этом контроллеру? Если да, то...кто должен поменять текущий кадр? Контроллер? Явно не модель, т.к. модель не отвечает за текущий кадр. Или контроллер вызывает функцию View.update(deltaTime), в которой идет вычисление следующего кадра? А в контроллере идет лишь вычисление deltaTime?
Да никто. Мельница не несёт нагрузки смысловой, важной для всего приложения: поэтому пусть её крутит только вью, да как захочет.
Старый 04.12.2011 22:32 imena вне форума
imena
т.е., я правильно понял? если мы добавляем на сцену какой-либо объект, то...

Вариант 1: Вьюшка может добавить графическое отображение этого объекта, сказать контроллеру что она добавила, а контроллер говорит моделе чтобы она просто-тупо ЗАФИКСИРОВАЛА у себя что был добавлен такой-то объект по таким-то координатам. И в этом случае моделе нет необходимости уведомлять вьюшку. Так?

Вариант 2: Мы кликаем... контроллер отлавливает, модель добавляет и говорит вьюшке и ТОЛЬКО ПОСЛЕ ЭТОГО вьюшка добавляет на сцену объект.

Так? Т.е. тут никаких правил?
Старый 05.12.2011 00:10 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
Примерно так. Просто важно разделить данные от отображения от логики.
И здесь и надо провести грань логики от отображения: например, если это аудиоплеер - следующий случайный трек выбирает контроллер, пишет в модель, а она уведомляет вьюшку (эти действия контроллер начинает, кстати, по событию от вьюшки). С другой стороны, контроллеру наплевать, что у главного героя задымилась голова, например, если это просто эффект.
Старый 05.12.2011 01:54 imena вне форума
imena
Спасибо. Теперь хоть как-то что-то понятно... хотя, есть еще вопросы. Но их позже задам ))
Старый 21.06.2012 16:11 tsarapkabel вне форума
tsarapkabel
 
Аватар для tsarapkabel
Цитата:
Код AS3:
public class Model extends EventDispatcher implements IReadableModel
Для чего тут интерфейс? Запутать читателя?
Старый 21.06.2012 16:40 Котяра вне форума
Котяра
 
Аватар для Котяра
Для того, чтобы виды могли ТОЛЬКО читать модель.
Но дальше, видать забыл/забил.
Должно быть
Код AS3:
private var _model:IReadableModel;
..
public function View(model:IReadableModel) {
Ну и читай последний абзац.
Обновил(-а) Котяра 21.06.2012 в 16:50
Старый 21.06.2012 18:41 tsarapkabel вне форума
tsarapkabel
 
Аватар для tsarapkabel
Цитата:
Для того, чтобы виды могли ТОЛЬКО читать модель.
Цитата:
Однако, мы всё равно можем изменить элемента массива colors.
Вот я и говорю — запутать читателя, ибо оно тут не действует.

И если сделать
Код AS3:
_model:IReadableModel;
теряется EventDispatcher и addEventListener уж не навесишь.
Старый 21.06.2012 19:00 КорДум вне форума
КорДум
 
Аватар для КорДум
Код AS3:
IReadableModel extends IEventDispatcher
Старый 21.06.2012 22:06 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
Об этом сказано ниже
Цитата:
View не может изменять Model, как вы уже поняли, а у нас может. Как это исправить? Вью нужно передавать не саму модель, а только интерфейс, в которым "порезаны" все set методы. Например, в нашем случае такой
Старый 22.06.2012 17:56 tsarapkabel вне форума
tsarapkabel
 
Аватар для tsarapkabel
Спасибо! С кодом:
Код AS3:
private var _model:IReadableModel;
...
public function View(model:IReadableModel) {
...
IReadableModel extends IEventDispatcher
всё встало на свои места (хотя и в тексте то же самое написано).
Старый 22.06.2012 23:09 gloomyBrain вне форума
gloomyBrain
 
Аватар для gloomyBrain
На самом деле можно не делать IReadableModel, и вообще модель не передавать в отображение. И сточки зрения безопасности такой способ будет ну совсем безопасным (view не сможет писать в model потому что ссылки вообще нет).
Для того чтобы у view не было ссылки на model, как правило, делается медиатор, который слушает события модели и заставляет view показать то что нужно.
Старый 14.06.2013 17:20 Akopalipsis вне форума
Akopalipsis
Спасибо ,за чудесную статью! Но для полного понимания - обьясните пожалуйста глупому мне - что нужно в классе BaseController создавать, экземпляр Model или IModel ?
Старый 14.06.2013 17:59 СлаваRa вне форума
СлаваRa
 
Аватар для СлаваRa
IModel - интерфейс, его экземпляр создать невозможно, в отличие от Model, который его реализует
Старый 01.04.2015 11:49 gyfak вне форума
gyfak
Спасибо за статью.
 

 


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


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