Инструкция, как последовательность методов

от автора

Привет, Хабр!

Пишу впервые, немного нервничаю, поэтому уже в четвертый раз набираю первое предложение.
Я занимаюсь разработкой игр. Преимущественно на флеше. Больше года работал с HaXe NME — круто. Но много своих «выкрутасов», которые, если сравнивать с AS3 при таргете во флеш, можно уместить в отдельную статью.
За более чем два года опыта выделил одну насущную для меня проблему при создании флеш игр.
Это первичная инициализация. Проблема заключается в том, что пока пользователь смотрит на прогресс-бар прелоадера, в бэкграунде выполняется множество операций — от загрузки данных и ассетов до их подготовки. И, зачастую, количество этих операций зашкаливает даже в самом начале разработки, не говоря уже об обновлениях.

Очень много смотрел чужие исходники игр и видел ужасные нагромождения конструкций вида:

/**  * calling server (pseudo-code)  */ private function startInitialization():void { 	ServerConnection.instance.call('method', params, onCallMethodComplete); }  private function onCallMethodComplete():void { 	ServerConnection.instance.call('methodTwo', params, onCallMethodTwoComplete); }  private function onCallMethodTwoComplete():void { 	AssetsLoader.loadAssetZipByName('assetName', onAssetLoaded); }  private function onAssetLoaded():void { 	// etc } 

При этом, не всегда пользуются какими-то вспомогательными синглтон-классами в методах, и зачастую в методах класса загрузки появляется куча loader-ов для каждой операции со всеми вытекающими слушателями. Мешанина не читаемая абсолютно. Сам так же делал когда-то.
На вкус и цвет, как говорится, но когда количество методов для инициализации вырастает хотя бы до десятка — читать такой код становится трудно. А если впоследствии внезапно понадобится вставить дополнительную промежуточную функцию, скажем, для дополнительной обработки полученных данных, — это превращается в головную боль и уйму времени, потраченного на попытки разобраться что за чем идёт.
Такая проблема актуальна не только для загрузки данных. А так же, например, последовательной анимации элементов интерфейса. Кто-то возразит, что анимировать можно и во Flash IDE — спору нет. Однако, зачастую при анимации появления окна требуется не только показывать элементы по порядку, но ещё и активировать/деактивировать их, чтобы не возникало случайных закрытий окна по пользовательскому клику до завершения анимаций.
Задача вроде бы понятная и достаточно тривиальная. Вижу цель. Иду к цели.
Утилитка, которую я написал, — весьма небольшая и вполне себе простая. Однако, она очень помогла мне перейти от конструкций, приведенных выше, к вполне лаконичному:

private function init():void { 	Instruction.create() 	.add(configureViewTree) 	.add(initializeManagers) 	.add(SocialData.instance.init, flashVars) 	.add(GameData.instance.init, 'http://server.ru/', 'http://static.server.ru/assets/') 	.add(Assets.getZip, 'music/music.zip') 	.add(MetaData.instance.init) 	.add(World.instance.create, worldContainer) 	.execute(startGame); } 

Все методы вызовутся в той же последовательности, в которой они добавлены.
Последовательность действий плюс относительно удобная читаемость очереди. Казалось бы, что ещё нужно?!

Полный код класса Instruction

package {	 	/** 	 * ... 	 * @author Frost 	 */ 	public class Instruction { 		 		public static function create():Instruction { 			return new Instruction(); 		} 		 		private var functionsQueue:Vector.<Function>; 		private var argumentsQueue:Vector.<Array>; 		 		public function Instruction() { 			functionsQueue = new Vector.<Function>(); 			argumentsQueue = new Vector.<Array>(); 		} 		 		public function add(calledFunction:Function, ...params):Instruction { 			functionsQueue.push(calledFunction); 			argumentsQueue.push(params); 			return this; 		} 		 		public function execute(completeHandler:Function = null, ...params):void { 			add(completeHandler, params).checkQueue(); 		} 		 		private function doTask():void { 			var calledFunctionArguments:Array = argumentsQueue.shift(); 			calledFunctionArguments.unshift(checkQueue); 			functionsQueue.shift().apply(null, calledFunctionArguments); 			calledFunctionArguments = null; 		} 		 		private function checkQueue():void { 			if (functionsQueue.length > 1) { 				doTask(); 				return; 			} 			completeInstruction(); 		} 		 		private function completeInstruction():void { 			if (functionsQueue[0] != null) { 				functionsQueue.shift().apply(argumentsQueue.shift()); 			} 			functionsQueue = null; 			argumentsQueue = null; 		} 	} } 

Минусов у подобной структуры несколько. Во-первых, функции, которые мы хотим запихнуть в очередь, должны соответствовать определённой структуре, а именно — первым параметром идёт колбек на завершение метода, остальные параметры — это аргументы. Положительным моментом может служить то, что колбек можно запихнуть как на загрузку данных в обработчик Event.COMPLETE, так и просто поставить его вызов последним, после проведения различных операций:

function weWantToAddToInstruction(completeHandler:Function, paramOne:Object, paramTwo:String, paramThree:Function, ...params):void { 	// do some crazy stuff 	completeHandler(); } 

Так же, к минусу можно отнести и то, что обработка ошибок в выполняемой инструкции лежит на плечах выполняемых методов. Я пока не придумал решение, которое позволит вынести отлов ошибок под ответственность самой инструкции. В вариантах, что я пробовал — приходилось сильно менять структуру функций => не удобно. Да и во многих ситуациях всё заходило в тупик. Так-то было бы здорово.

Ещё одним, но побочным эффектом, оказалось то, что, начав использовать инструкцию, я стал часто плодить анонимные функции внутри вызываемых методов, чтобы до вызова completeHandler сработала ещё какая-нибудь функция или выставление флага, что не правильно. Но это, скорее, результат моей лени. Т.к. в таких случаях внутри метода, который вызывается внутри инструкции, — можно создать ещё одну инструкцию и упорядочить код, обойдя стороной анонимки.

Положительным аспектом стало то, что в инструкцию можно запихивать методы, аргументы для которых — абсолютно произвольные, как по содержанию, так и по количеству. Это весьма удобно. Но лишь в том случае, если твёрдо уверен, что аргументы — именно те, которые задуманы. Хотя, это правило применимо ко всему, наверное.

В общем-то это всё. Благодарю за внимание, хабражители.
Очень здорово, если то, что я написал, будет кому-то полезно.

ссылка на оригинал статьи http://habrahabr.ru/post/207308/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *