27 августа 2012 в 12:46

Пишем реализацию MVC для Backbone

image

Одним пасмурным утром я подумал, что было бы неплохо хорошенько прорефакторить один из моих старых проектов. Это некоммерческое легковесное приложение для кастомизации HUD в одном 3Д шутере. Писал я его 2 года назад, был горяч и неопытен. В результате куча отменного спагетти-кода, который, несмотря на все свои недостатки, делал своё дело. Став мудрее и опытнее, я решил полностью переписать приложение, дать ему новую архитектуру, упростить поддержку и обновление. Как это сделать? Ответ казался простым — использовать MVC, разделить на уровни и связать всё в единое целое. Так я столкнулся с проблемой выбора простого и эффективного фреймворка, который станет прочным фундаментом. После быстрого исследования я выбрал backbone.js. Очень понравился своей простотой и гибкостью. Можно просто открыть исходники и понять, как всё устроено и работает. Единственный нюанс, который не радовал — MV-паттерн. Размазывать логику по многочисленным views очень не хотелось, так родилась идея написать свой велосипед, который предоставит недостающие части головоломки. Плюс, создание чего-то нового — это всегда увлекательно и интересно. Недолго думая, я приступил к реализации контроллеров для backbone.

Постановка задачи и реализация базовых методов


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

Скелет контроллера будет выглядеть вот так:
Controller = {

    views: {}, // views hash map
    models: {}, // models hash map
    collections: {}, // collections hash map

    // Set of methods to get existing view, get view constructor and create new view using constuctor
    getView: function() {}, 
    getViewConstructor: function() {},
    createView: function() {},

    // Set of methods to get existing model, get model constructor and create new model using constuctor
    getModel: function() {},
    getModelConstructor: function() {},
    createModel: function() {},

    // Set of methods to get existing collection,
    // get collection constructor and create new collectionusing constuctor
    getCollection: function() {},
    getCollectionConstructor: function() {},
    createCollection: function() {},

    // This method will subscribe controller instance to view events
    addListeners: function() {}
}


Пока что всё очень просто. Но для сложного приложения нам необходимо иметь несколько контроллеров, желательно, чтобы набор коллекций и моделей был общим для всего приложения. Так нам на помощь приходит Application — базовый конструктор, который объединит контроллеры в единое приложение.

Скелет приложения будет выглядеть так:
Application = {
    //Method that will initialize all controllers upon applicaiton launch
    initializeControllers: function() {},

    // Set of methods to get existing model, get model constructor and create new model using constuctor
    getModel: function() {},
    getModelConstructor: function() {},
    createModel: function() {},

    // Set of methods to get existing collection, get collectionconstructor and create new collectionusing constuctor
    getCollection: function() {},
    getCollectionConstructor: function() {},
    createCollection: function() {},
}

Так же было бы полезно сразу же создавать экземпляры всех коллекций в момент старта приложения. А ещё было бы неплохо вызывать callback-функцию у каждого контроллера после того, как приложение стартует. Этот callback нужно вызвать в тот момент, когда все предварительные данные готовы. Таким образом, каждый контроллер будет «знать», что приложение готово к работе. Не долго думая, добавляем методы:
// Create collection instances upon application start
Application.buildCollections()

// Initialize all application controllers
Application.initializeControllers()


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

EventBus = {
    // Function to add event listeners
    addListeners: function() {},

    // Function to fire event listneres
    fireEvent: function() {}
}


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

Реализация Application


Начнём с главного конструктора — Application. Базовый класс реализуем тем же способом, как это делает backbone.
var Application = function(options) {
    _.extend(this, options || {});

    // Create a new instance of EventBus and pass the reference to out application
    this.eventbus = new EventBus({application: this});

    // Run application initialization if needed
    this.initialize.apply(this, arguments);

    // Create documentReady callback to lauch the application
    $($.proxy(this.onReady, this));
};


Далee, используя _.extend, расширяем прототип:
_.extend(Application.prototype, {

        // Hash maps to store models, collections and controllers
	models: {},
	collections: {},
	controllers: {},

	/**
	 * Abstract fuction that will be called during application instance creation
	 */
	initialize: function(options) {
		return this;
	},

	/**
	 * Called on documentReady, defined in constructor
	 */
	onReady: function() {
		// initialize controllers
		this.initializeControllers(this.controllers || {});
		// call to controller.onLauch callback
		this.launchControllers();
		// call application.lauch callback
		this.launch.call(this);
	},

	/**
	 * Function that will convert string identifier into the instance reference	 
	 */ 
	parseClasses: function(classes) {
		var hashMap = {};

		_.each(classes, function(cls) {
			var classReference = resolveNamespace(cls),
				id = cls.split('.').pop();

			hashMap[id] = classReference;
		}, this);

		return hashMap;
	},

	/**
	 * Abstract fuction that will be called during application lauch
	 */
	launch: function() {},

	/**
	 * Getter to retreive link to the particular controller instance
	 */
	getController: function(id) {
		return this.controllers[id];
	},

	/**
	 * Function that will loop throught the list of collection constructors and create instances
	 */
	buildCollections: function() {
		_.each(this.collections, function(collection, alias) {
			this.getCollection(alias);
		}, this);
	}
});


Для инициализации наших контроллеров, нам понадобится два метода. Application.initializeControllers создаст экземпляры и вычитает наборы коллекций и моделей, чтобы сохранить ссылки непосредственно в самом приложении. А Application.launchControllers пройдётся по уже созданным конроллерам и выполнит Controller.onLaunch callback.
_.extend(Application.prototype, {
    ...    
	/**
	 * Fuction that will loop through all application conrollers and create their instances
	 * Additionaly, read the list of models and collections from each controller
         * and save the reference within application
	 */
	initializeControllers: function(controllers) {
		this.controllers = {};

		_.each(controllers, function(ctrl) {
			var classReference = resolveNamespace(ctrl),
				id = ctrl.split('.').pop();

                        // create new Controller instance and pass reference to the application
			var controller = new classReference({
				id: id,
				application: this
			});

			controller.views = this.parseClasses(controller.views || []);

			_.extend(this.models, this.parseClasses(controller.models || []));
			_.extend(this.collections, this.parseClasses(controller.collections || {}));

			this.buildCollections();
			this.controllers[id] = controller;
		}, this);
	},

	/**
	 * Launch all controllers using onLauch callback
	 */
	launchControllers: function() {
		_.each(this.controllers, function(ctrl, id) {
			ctrl.onLaunch(this);
		}, this);
	}
    ...
});


Чтобы обеспечить коммуникацию между контроллерами и дать возможность подписаться на события от конкретных копмонентов, добавим метод Application.addListeners, который делегирует работу в наш EventBus:
_.extend(Application.prototype, {
    ...
	/**
	 * Abstract fuction that will be called during application lauch
	 */
	addListeners: function(listeners, controller) {
		this.eventbus.addListeners(listeners, controller)
	}    
    ...
});


Для работы с моделями и коллекциями нам понадобятся функции для получения ссылки на экземпляр, ссылки на конструктор и метод создания новой сущности. Рассмотрим конкретную реализацию на примере моделей, для коллекций функции будут работать аналогично.
_.extend(Application.prototype, {
    ...    
	/**
	 * Getter to retreive link to the particular model instance
	 * If model instance isn't created, create it
	 */
	getModel: function(name) {
		this._modelsCache = this._modelsCache || {};

		var model = this._modelsCache[name],
			modelClass = this.getModelConstructor(name);

		if(!model && modelClass) {
			model = this.createModel(name);
			this._modelsCache[name] = model;
		}

		return model || null;
	},

	/**
	 * Getter to retreive link to the particular model consturctor
	 */
	getModelConstructor: function(name) {
		return this.models[name];
	},

	/**
	 * Function to create new model instance
	 */
	createModel: function(name, options) {
		var modelClass = this.getModelConstructor(name),
			options = _.extend(options || {});

		var model = new modelClass(options);

		return model;
	},

	/**
	 * Getter to retreive link to the particular collection instance
	 * If collection instance isn't created, create it
	 */
	getCollection: function(name) {
		...
	},

	/**
	 * Getter to retreive link to the particular collection consturctor
	 */	
	getCollectionConstructor: function(name) {
		...
	},
	/**
	 * Function to create new collection instance
	 */	
	createCollection: function(name, options) {
		...
	},    
    ...
});


Теперь, наш базовый конструктор для приложения готов. Следует упомянуть метод Application.parseClasses. Дело в том, что я решил передавать списки контролеров, моделей, коллекций и вью в виде массива строк. Получая на входе
[
    'myApplication.controller.UserManager',
    'myApplication.controller.FormBuilder'
]

функция Application.parseClasses превратит этот массив в маппинг
{
    'UserManager': myApplication.controller.UserManager,
    'FormBuilder': myApplication.controller.FormBuilder
}


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

Реализация Controller


Контроллер получит немного более простой код, так всю работу с моделями и коллекциями мы делегируем на Application. Для начала объявление:

var Controller = function(options) {
	_.extend(this, options || {});
	this.initialize.apply(this, arguments);
};

А дальше можно расширять прототип
_.extend(Controller.prototype, {
	views: {},
	models: {},
	collections: {},

	initialize: function(options) {
	},

	/**
	 * Add new listener to the application event bus
	 */
	addListeners: function(listeners) {
		this.getApplication().addListeners(listeners, this);
	},

	/**
	 * Abstract fuction that will be called during application lauch
	 */	
	onLaunch: function(application) {
	},

	/**
	 * Getter that will return the reference to the application instance
	 */	
	getApplication: function() {
		return this.application;
	}
});


Добавляем методы для работы с Views:
_.extend(Controller.prototype, {
    ...

	/**
	 * Getter that will return the reference to the view constructor
	 */		
	getViewConstructor: function(name) {
		return this.views[name];
	},

	/**
	 * Function to create a new view instance
	 * All views are cached within _viewsCache hash map
	 */
	createView: function(name, options) {
		var view = this.getViewConstructor(name),
			options = _.extend(options || {}, {
				alias: name
			});

		return new view(options);
	}
    ...
});


Работу с моделями и коллекциями мы делегируем на наш Application
_.extend(Controller.prototype, {
    ...
	/**
	 * Delegate method to get model instance reference
	 */		
	getModel: function(name) {
		return this.application.getModel(name);
	},

	/**
	 * Delegate method to get model constructor reference
	 */		
	getModelConstructor: function(name) {
		return this.application.getModelConstructor(name);
	},

	/**
	 * Delegate method to create model instance
	 */		
	createModel: function(name, options) {
		return this.application.createModel(name)
	},

	/**
	 * Delegate method to get collection instance reference
	 */		
	getCollection: function(name) {
		return this.application.getCollection(name);
	},

	/**
	 * Delegate method to get collection constructor reference
	 */		
	getCollectionConstructor: function(name) {
		return this.application.getCollectionConstructor(name);
	},

	/**
	 * Delegate method to create collection instance
	 */		
	createCollection: function(name, options) {
		return this.application.createCollection(name);
	}    
    ...
});


И под конец, позволим нашим контроллерам общаться, используя Application.EventBus
_.extend(Controller.prototype, {
    ...
	/**
	 * Delegate method to fire event
	 */		
	fireEvent: function(selector, event, args) {
		this.application.eventbus.fireEvent(selector, event, args);
	}    
    ...
});

Базовый конструктор для контроллера готов! Осталось совсем немного :)

Реализация EventBus


Для начала опишем конструктор. Чтобы дать возможность контроллеру слушать события от view, нам необходимо немного расширить базовый прототип Backbone.View. Дело в том, что нам нужен некий селектор, по которому будут отслеживаться события. Для того введём свойство alias, которое автоматически будет присваиваться в момент создания компонента. И добавим метод fireEvent, который вызовет «родной» View.trigger() и нотифицирует EventBus о новом событии.

var EventBus = function(options) {
	var me = this;

	_.extend(this, options || {});

        // Extend Backbone.View.prototype
	_.extend(Backbone.View.prototype, {
		alias: null,

		/*
		 * Getter that wll return alias
		 */
		getAlias: function() {
			return this.options.alias;
		},

		/*
		 * Instead of calling View.trigger lets use custom function
		 * It will notify the EventBus about new event
		 */
		fireEvent: function(event, args) {
			this.trigger.apply(this, arguments);
			me.fireEvent(this.getAlias(), event, args);
		}
	});
};


Теперь можно смело расширять прототип. Используем EventBus.addListeners для подписки на новые события, а EventBus.fireEvent надёт нужный обработчик и выполнит его.
_.extend(EventBus.prototype, {
	// Hash Map that will contains references to the all reginstered event listeners
	pool: {},
	/**
	 * Function to register new event listener
	 */ 
	addListeners: function(selectors, controller) {

		this.pool[controller.id] = this.pool[controller.id] || {};
		var pool = this.pool[controller.id];

		if(_.isArray(selectors)) {
			_.each(selectors, function(selector) {
				this.control(selector, controller);
			}, this)
		}
		else if(_.isObject(selectors)) {
			_.each(selectors, function(listeners, selector) {
				_.each(listeners, function(listener, event) {
					pool[selector] = pool[selector] || {};
					pool[selector][event] = pool[selector][event] || [];

					pool[selector][event].push(listener);

				}, this);
			}, this)

		}
	},

	/**
	 * Function to execute event listener
	 */ 	
	fireEvent: function(selector, event, args) {
		var application = this.getApplication();

		_.each(this.pool, function(eventsPoolByAlias, controllerId) {
			var events = eventsPoolByAlias[selector];

			if(events) {
				var listeners = events[event]
					controller = application.getController(controllerId);

				_.each(listeners, function(fn) {
					fn.apply(controller, args);
				});
			}


		}, this);
	},

	/**
	 * Getter to receive the application reference
	 */ 	
	getApplication: function() {
		return this.options['application'];
	}
});


Ура! Теперь все основные части реализованы! Заключительный штрих
Application.extend = Backbone.Model.extend;
Controller.extend = Backbone.Model.extend;

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

Документация и примеры



Исходные файлы и документация на оффициальной странице Backbone.Application

Так же я создал простой пример — это классический ToDo с использованием MVC. Исходники и коментарии к реализации можно посмотреть здесь — github.com/namad/Backbone.Application/blob/master/examples/ToDo/js/todos.js

И в качестве бонуса более сложный пример, ради которого я написал весь этот велосипед — visualHUD, редактор HUD для моей любимой игры Quake Live. На данный момент новая версия всё ещё в разработке, необходимо доделать кучу мелочей, но в целом весь функционал работает и его можно пощупать своими руками. Кому интересно, исходинки старой версии на гуглокоде

P.S. Это моя первая статья подобного характера и я понятия не имею, что получилось :) Так что любой адекватный отзыв на вес золота. Спасибо!
+5
6205
84
qbique 22,7

Комментарии (33)

0
MrSLonoed, #
Емнип когда то в бэкбоне были контроллеры. Но от них избавились, заменив классом Backbone.Router. Это более удобно для фронтэнда.

Еще не очень понял зачем это:
Application.extend = Backbone.Model.extend;
Controller.extend = Backbone.Model.extend;

0
qbique, #
Application.extend = Backbone.Model.extend;
Controller.extend = Backbone.Model.extend;
Это для того, чтобы можно было сделать так
myController = Backbone.Controller.extend({})
+1
qbique, #
Router — это хорошо, но выполняет немного другие задачи.
+3
MrSLonoed, #
Роутер — это та функциональность контроллеров, которую нельзя было выкинуть.Но сами контроллеры излишни. Нет ни одной причины, почему вьюшки не могут подписываться на изменение моделей.
Конечно связанность чуть больше, но мы не плодим сущностей, которые только и делают, что перенаправляют эвенты.
0
qbique, #
Я не оспариваю подход, я всего лишь показываю пример того, как можно решить проблему. Сам по себе backbone.application — это учебное пособие в каком-то смысле. Спорить о преимуществах MV перед MVC я не буду, не вижу в этом смысла.

0
MrSLonoed, #
Никакого оспаривания. Когда я начал заниматься бэкбоном у меня сразу возник вопрос а где контроллер Оо?, и идея реализовать свой.
0
qbique, #
В моём случае, я использовал backbone исключительно в определённых целях (см. visualHUD в примерах). Использовать роутер в качестве контроллера я не решился. Зато я решил развлечься и написать простенький application/controller. Результатом я остался доволен, пережитым опытом делюсь на хабре. Такие дела )
+1
Tenphi, #
3-4 года назад меня «били чайником по голове» за аналогичное мнение. Все меняется и это хорошо)
0
MrSLonoed, #
За идею выкинуть контроллер?)
Ну без него действительно непривычно поначалу.
0
Tenphi, #
Нет, конечно. К чему в рабочем приложении проводить такой нешуточный рефакторинг. Разговор шел об архитектуре грядущего проекта. А я как раз экспериментировал с созданием фреймворка, где вьюхи могли с удобством сочитать в себе функции контроллеров. Другим экспериментом было использование исключительно иерархической событийной модели для объектов находящихся в DOM.
0
mrjj, #
Даже простые вьюшки получаются очень здоровыми, имеет смысл рассматривать любые решения, которые это изменят.
+1
mrjj, #
Спасибо за статью, сейчас мало информации о дизайне приложений на backbone, и она очень ценная.

Честно говоря, мне немного подвывихнул мозг способ хранения/получения моделей и коллекций между контроллерами и приложением. Нет ли путей для упрощения подобный схемы?
0
qbique, #
Честно говоря, мне немного подвывихнул мозг способ хранения/получения моделей и коллекций между контроллерами и приложением. Нет ли путей для упрощения подобный схемы?

Не совсем понял, в чём сложность? Модели и коллекции хранятся и создаются приложением. Контроллер просто вызывает функцию-делегат и всё. Или я что-то не понял? :)
0
mrjj, #
а зачем в контроллере объявлены
views: {}, models: {}, collections: {},
и действительно ли необходима такая большая пачка делегатов? (хотя она и положительно сказывается на инкапсуляции)
0
qbique, #
а зачем в контроллере объявлены views: {}, models: {}, collections: {},
для того, чтобы при объявлении контроллера не приходилось лезть в application. таким образом добавление модели или коллекции будет осуществляться в том месте, где это действительно нужно, без лишних телодвижений.
+3
ArtyomTrityak, #
Получился еще один велосипед.

Backbone — это скорее MTV, чем MVC, и превращать его в MVC лично я не вижу никакого смысла.
Вы просто пытаетесь подменить понятия.

Если воспринять Router как Application, View как Controller, Template как View, то все станосится на свои места.

Я считаю, что это все просто не нужно — для небольших приложений достаточно иерархии Router (Application) -> Modules (view + list view + model + template) x n.
Для больших приложений есть 3 варианта:

* Marionette (https://github.com/derickbailey/backbone.marionette) — реализует все что вам нужно + еще кучу всего + коммунити
* Aura (https://github.com/addyosmani/aura) — для виджет-ориентированных приложений
* Использовать аккуратно Router (Application) -> Modules ( (view + list view + model + template) x n раз ) + require.js для AMD

Поверьте, этого достаточно.

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

+2
morr, #
Если кратко, то ваш контроллер делает:
1) вводит свой менеджер событий, причём не следуя конвенциям и бекбона, и джейквери, и многих других современных библиотек, — addListeners и fireEvent вместо лаконичных on и trigger.
2) завуалированно и в особо извращённой форме хранит глобальные объекты, доступные в единственном экземпляре
this.application.getController('Viewport').getView('Canvas').

От подобного делегирования меньшей связанности не будет, наоборот оно лишь поощряет использования глобальных объектов.
3) создаёт объекты через строковые литералы для геттеров из пункта 2. и ещё, как я понимаю, чтобы передать параметры в конструктор, придётся создавалку переопределять.

С пунктом 1 отлично справляется view, который будет сверху всех остальных, который будет содержать в себе прочие view. А пункты 2 и 3 вовсе вредны.
Про контроллеры бекбона выше верно написали, их в прошлом выпилили потому, что всё, кроме взаимодействия с урлами, дублирует логику Backbone.View

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

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

Вот чего в действительности не хватает бекбону, это:
1) более мощного роутера, чтобы можно было задавать опциональные параметры урлам и накладывать ограничения на содержимое параметров.
2) из коробки механизма отношений сежду моделями и коллекциями, подобно тому как в backbone.relational плагине сделано(только там реализация не самая лучшая).
3) из коробки механизма рендеринга партиалов
0
qbique, #
2) завуалированно и в особо извращённой форме хранит глобальные объекты, доступные в единственном экземпляре
тут определённые сложности, да )

3) создаёт объекты через строковые литералы для геттеров из пункта 2. и ещё, как я понимаю, чтобы передать параметры в конструктор, придётся создавалку переопределять.
на данном этапе там хранятся ссылки только на конструкторы, передача параметров происходит в момент создания сущностей.

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

а так да, велосипед-велосипедом.
0
sdevalex, #
Зачем свой велосипед для этого???
EventBus = { ... }


У Backbone есть своё отличное решение backbonejs.org/#Events
EventBus = { };
_.extend(EventBus, Backbone.Events);
0
qbique, #
Потому что, событие не так просто отследить, если добавлять его в лоб через Backbone.Event.on
Для того, чтобы понять, какой хендлер вызвать, нужно знать
1. Какой контроллер подписан
2. Какой вью генерирует событие
3. Какой именно хендлер нужно вызвать
Поэтому пул событий достаточно сложный, простой подписки через Backbone.Event.on оказалось недостаточно
–1
kuzemchik, #
Мне не нравится.

Размазывать логику по многочисленным views очень не хотелось

Логика должна быть в моделях. Или это не MVC.

Так же требуется возможность создавать компоненты (views) и иметь возможность слушать их события

События надо слушать у моделей. Или это не MVC.

Из гибкого backbone вы сделали фреймворк для монолитных, тяжело изменяющихся приложений. Прелесть backbone в его гибкости, иначе теряется смысл его использовать.
0
qbique, #
Логика должна быть в моделях. Или это не MVC.
Модель — это данные, там должна быть логинка по модификации этих данных, а роль конроллера — бизнес-логика и связи

Из гибкого backbone вы сделали фреймворк для монолитных, тяжело изменяющихся приложений.
Обоснуете?
0
Silver_Clash, #
На счет модели и контроллера — вопрос спорный. Не зря многие стремятся сделать толстые модели и тонкие контроллеры.
0
qbique, #
правда в том, что нет серебряной пули. не вижу в моём подходе противоречий в вашими высказываниями.
0
kuzemchik, #
Модель — это данные, там должна быть логинка по модификации этих данных, а роль конроллера — бизнес-логика и связи

Ох… Вы совершенно неправы…
За бизнес-логику в контроллерах надо бить по рукам.

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

blog.astrumfutura.com/2008/12/the-m-in-mvc-why-models-are-misunderstood-and-unappreciated/
www.littlehart.net/atthekeyboard/2007/04/27/fat-models-skinny-controllers/
www.bennadel.com/blog/2379-A-Better-Understanding-Of-MVC-Model-View-Controller-Thanks-To-Steven-Neiland.htm
msdn.microsoft.com/en-us/library/ff649643.aspx
www.yiiframework.com/doc/guide/1.1/en/basics.best-practices
odetocode.com/Blogs/scott/archive/2009/04/06/putting-the-m-in-mvc-part-iii.aspx
gayleforce.wordpress.com/2009/11/28/model-view-controller-mvc-where-do-you-put-your-business-logic/
stackoverflow.com/questions/235233/asp-net-mvc-should-business-logic-exist-in-controllers
stackoverflow.com/questions/212027/where-are-the-business-rules-in-mvc

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

Обоснуете?

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

Представления backbone прекрасны, потому что они говорят «я рисуюсь и делаю то-то». И не важно куда вставили это представление. Ему дают модель, и оно с ней работает. Такое представление может быть частью другого, и ничего об этом не знать.

У вас же есть контроллер который за всеми следит, и если вам нужно будет часть интерфейса в другом месте, вы будете дублировать этот код в другом контроллере.
0
qbique, #
пардон, промазал — habrahabr.ru/post/149626/#comment_5092288
0
qbique, #
Вообще, моё решение не навязывает какой-то определённый подход. Я всего лишь добавил недостающую сущность, которая позволит разгрузить View от избыточной логики, которую можно переложить на контролеры.
0
kuzemchik, #
А какую логику в представлении вы считаете избыточной? Если можно с примером.
0
kuzemchik, #
Не считая бизнес-логики естественно (с ней и так все ясно)
0
qbique, #
Например, в том же ToDo роль контролера выполнял главный view, он слушал события и принимал решения. При наличии контролера, так делать не обязательно, пусть контролер думает. Минус один жирный вью, остальные могут быть более атомарными, ещё в плюсах простой контролер.
0
qbique, #
Более сложный пример может быть связан с получением целого набора данных. Например, пользователь перешёл в некий раздел, для работы в котором нам необходимо получить данные для модели, с которой будет работать пользователь, потом загрузить пару дополнительных словарей. Я бы всю эту логику держал бы в контролере. Рендерим блок, показываем индикатор загрузки (можно заблокировать работу оверлеем), делаем серию запросов и по результатам передаём данные внутрь view и начинам работу. Таким образом вью не разруливает загрузку, а только получает на вход сеты данных. Это значит, что мы можем использовать компонент где угодно и сколько угодно раз, просто создавая его и передавая сеты данных, которые ему нужны для работы.
0
qbique, #
Если такая подгрузка является типичной (те разные контроллеры могут потребовать эти действия, а отличатся будут только параметрами), то есть смысл создать отдельный хелпер (библиотеку, да как угодно) и делать вызовы из контроллера

Data.Helper.loadData(itemId, callback)
0
qbique, #
За бизнес-логику в контроллерах надо бить по рукам.
Пожалуй соглашусь.

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

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

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