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

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

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

State (Состояние)

Запись от Dukobpa3 размещена 27.12.2013 в 01:24
Обновил(-а) Dukobpa3 31.12.2013 в 13:53

Опять же для затравочки.
Ссылка на годный плюсовый пример(зауважал этот сайт в последнее время, хорошо пишут)
Примеры разных паттернов
Пример реализации стейта
Видео от этого чувака по паттернам

Поехали.
Опираться буду на пример паттернкрафта. Он вполне достойный.
Итак, в предыдущей статье мы рассмотрели паттерн стейт-машины. Он нам нужен если у объекта есть несколько состояний, которые меняют поведение объекта.
Но мы рассмотрели примитивный пример, который затрагивает только два параметра: скорость и атаку. Такие варианты стейт-машин целесообразно решать именно так как я там описал. Простейший свитч и погнали.
Но рассмотрим ситуацию когда меняется не только парочка геттеров а и кусок поведения.

Цитата:
Паттерн State решает указанную проблему следующим образом:

- Вводит класс Context, в котором определяется интерфейс для внешнего мира.
- Вводит абстрактный класс State.
- Представляет различные "состояния" конечного автомата в виде подклассов State.
- В классе Context имеется указатель на текущее состояние, который изменяется при изменении состояния конечного автомата.
Т.е. суть паттерна в том что мы пишем некий интерфейс стейта, который во многом повторяет АПИ самого объекта.
Пишем несколько стейтов, которые имплементят этот интерфейс.
Даем этим объектам-стейтам ссылку на наш главный объект. Реализацию поведения главного объекта выносим из самого объекта в стейты.
Таким образом наш главный объект становится как бы прокси над текущим стейтом, а сам ничего делать не умеет.
Так же нам нужен некий контекст. Штука, которая занимается переключением стейтов. Можно контекст вшить в сам главный объект, а можем сделать как отдельной сущностью.

Ниже я буду описывать свою реализацию, за основу беру паттернкрафт и буду объяснять свои изменения. Предполагаю что код паттернкрафт вы смотрели.

Во-первых, мы таки добавим отдельный класс контекста. Следуя принципам single responsibility
- пусть танк решает задачи танка - ездит и стреляет.
- Контекст будет переключать нужное состояние.
- Стейт будет выдавать некий интерфейс с уникальной реализацией поведения для каждого состояния танка.

Итого из танка убрал механизмы переключения стейтов, это управление передается контексту:
Код AS3:
package patterncraft.state
{
    import flash.display.Sprite;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
 
    public class SiegeTank extends Sprite {
 
        public static const STATE_TANK:String = "stateTank";
        public static const STATE_SPEED:String = "stateSpeed";
        public static const STATE_SIEGE:String = "stateSiege";
 
        public static const STATE_NONE:String = "stateNone";
 
	    internal var messageTextField:TextField = new TextField();
	    internal var moveTextField:TextField = new TextField();
	    internal var attackTextField:TextField = new TextField();
 
	    private var _context:Context;
 
	    public function SiegeTank() {
            messageTextField.autoSize = TextFieldAutoSize.LEFT;
 
            moveTextField.y = 25;
            moveTextField.autoSize = TextFieldAutoSize.LEFT;
 
            attackTextField.y = 50;
            attackTextField.autoSize = TextFieldAutoSize.LEFT;
 
            addChild(messageTextField);
            addChild(moveTextField);
            addChild(attackTextField);
 
	        _context = new Context(this);
        }
 
        public function toTankMode():void {
            _context.setState(STATE_TANK);
        }
 
        public function toSiegeMode():void {
	        _context.setState(STATE_SIEGE);
        }
 
        public function toSpeedMode():void {
	        _context.setState(STATE_SPEED);
        }
 
        public function attack():void {
	        if(!_context.state) messageTextField.text = "Can`t attack during transition";
	        _context.state.attack();
        }
 
        public function move(targetX:Number, targetY:Number):void {
	        if(!_context.state) messageTextField.text = "Can`t move during transition";
	        _context.state.move(targetX, targetY);
        }
 
    }
}
Добавил сам контекст.
В его задачи входит контролировать возможность перехода из одного стейта в другой и реализовывать сам механизм перехода.
Принимает в конструктор ссылку на танк, но себе ее не сохраняет, только отдает стейтам, так как рулить танком - задача стейтов.
Выдает наружу геттер текущего стейта.
Код AS3:
package patterncraft.state
{
	import flash.events.Event;
 
	internal class Context
	{
		private var _currentState:String;
		private var _pendingState:String;
 
		private var _statesMap:Object;
		private var _transitionsMap:Object;
 
		public function Context(tank:SiegeTank)
		{
			// Заполним вспомогательній словарь стейтов по названию
			_statesMap = {};
			_statesMap[SiegeTank.STATE_TANK] = new TankState(tank);
			_statesMap[SiegeTank.STATE_SIEGE] = new SiegeState(tank);
			_statesMap[SiegeTank.STATE_SPEED] = new SpeedState(tank);
 
			// Зарегистрируем доступные переходы
			// Получится такая вот цепочка переходов: siege <--> tank <--> speed
			_transitionsMap = {};
			// Из состояния танка можно перейти в любые два других
			_transitionsMap[SiegeTank.STATE_TANK] = [SiegeTank.STATE_SIEGE, SiegeTank.STATE_SPEED];
			// Из осадного или скоростного режима можно только в состояние танка
			_transitionsMap[SiegeTank.STATE_SIEGE] = [SiegeTank.STATE_TANK];
			_transitionsMap[SiegeTank.STATE_SPEED] = [SiegeTank.STATE_TANK];
 
			// стартовое состояние
			_currentState = SiegeTank.STATE_TANK;
			state.apply();
		}
 
		public function setState(state:String):void {
 
			// Если пытаемся установить состояние которое уже установлено
			if (state == _currentState) {
				trace("Already in " + state + " Mode!");
				return;
			}
			// Либо сейчас происходит транзишн - просто ничего не делаем.
			if (_currentState == SiegeTank.STATE_NONE) {
				trace("Previous transition not complete!");
				return;
			}
 
			// Если в списке доступных транзишинов нету запрошенного
			if(_transitionsMap[_currentState].indexOf(state) == -1) {
				trace("Can`t go to "+ state + " from " + _currentState);
				return;
			}
 
			// Запускаем транзишн. Подписываемся на его окончание
			_statesMap[_currentState].addEventListener(Event.COMPLETE, onTransitionComplete);
			_statesMap[_currentState].transitionTo(_pendingState);
 
 
			_currentState = SiegeTank.STATE_NONE; // текущий ставим как транзишн
			_pendingState = state; // ставим ожидаемый стейт.
		}
 
		private function onTransitionComplete(event:Event):void {
 
			trace("transition complete");
			event.currentTarget.removeEventListener(Event.COMPLETE, onTransitionComplete);
 
			// Отмечаемся в самих переменных стейтов.
			_currentState = _pendingState;
			_pendingState = "";
 
			// По завершении транзишина ставим настройки нового стейта
			state.apply();
		}
 
		public function get state():ISiegeTankState { return _statesMap[_currentState]; }
	}
}
Интерфейс немного поправил. Стейт может сам себя применить. Унифицировал переход в стейты.
Код AS3:
package patterncraft.state
{
    internal interface ISiegeTankState
    {
        function get damage():Number;
 
        function get canMove():Boolean;
 
        function get color():uint;
 
        function move(targetX:Number, targetY:Number):void;
 
        function attack():void;
 
	    function transitionTo(state:String):void;
 
	    function apply():void;
    }
}
И вот один из стейтов. Есть таймер, который призван символизировать переход из одного в другой стейты.
Проигнорировал то что могут быть разные анимации перехода из состояния танка в скоростной режим и в режим осады. Сделал один таймер. Но это вроде не проблема.
Код AS3:
package patterncraft.state
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.TimerEvent;
	import flash.utils.Timer;
 
 
	internal class SiegeState extends EventDispatcher implements ISiegeTankState {
        private var siegeTank:SiegeTank;
 
        public function SiegeState(siegeTank:SiegeTank) {
            this.siegeTank = siegeTank;
        }
 
        public function get damage():Number { return 20; }
 
        public function get canMove():Boolean { return false; }
 
        public function get color():uint { return 0x00cc00; }
 
		public function apply():void {
			siegeTank.messageTextField.text = "Stats: " + " damage: " + damage + " canMove: " + canMove;
			siegeTank.moveTextField.text = "";
			siegeTank.attackTextField.text = "";
 
			siegeTank.graphics.clear();
			siegeTank.graphics.beginFill(color, .5);
			siegeTank.graphics.drawRect(0, 0, 300, 100);
			siegeTank.graphics.endFill();
		}
 
        public function move(targetX:Number, targetY:Number):void {
            siegeTank.moveTextField.text = "Can`t move :( ";
        }
 
        public function attack():void {
            siegeTank.attackTextField.text = "Attacking for " + damage;
        }
 
		public function transitionTo(state:String):void {
			var timer:Timer = new Timer(1000, 1);
			timer.addEventListener(TimerEvent.TIMER, onTransitionComplete);
			timer.start();
		}
 
		private function onTransitionComplete(event:TimerEvent):void {
			event.currentTarget.reset();
			dispatchEvent(new Event(Event.COMPLETE));
		}
    }
}
В оригинале неточность была в моменте переключения между стейтами.
- каждый стейт сам контролировал в какие стейты из него мы можем перейти а в какие нет. У меня этим занимается контекст.
- частично переход между стейтами был возложен на сам танк (applyState()). Этот момент вынесен в сами стейты.
- Если глобально то субъективно в моем варианте более четкая логика и разделение ролей.

Так же контекст и стейты сделаны интерналом, так как с точки зрения внешнего пользователя (программиста). Видеть внутренности танка не нужно. Создать новый танк и добавить на сцену - можно. Лезть к контекстам и стейтам - нет.

Как-то так. Надеюсь понятно. Мб потом подредактирую.
Всего комментариев 5

Комментарии

Старый 27.12.2013 07:23 Dukobpa3 вне форума
Dukobpa3
 
Аватар для Dukobpa3
Апдейтил текст.
Старый 27.12.2013 08:49 GBee вне форума
GBee
 
Аватар для GBee
Кстати, можно реальный пример на редактировании записи в блоге тут. Если ты блогер одни состояния записи, если нет - другие. ;-)
Старый 27.12.2013 14:02 Akopalipsis вне форума
Akopalipsis
GBee хочу уточнить.. Вы предлагаете прятать часть статей от тех, кто не ведет блогов??)
Старый 27.12.2013 14:18 Dukobpa3 вне форума
Dukobpa3
 
Аватар для Dukobpa3
Akopalipsis
GBee предлагает рассмотреть пример стейта на статьях в блоге))
Старый 27.12.2013 15:27 Akopalipsis вне форума
Akopalipsis
Цитата:
GBee предлагает рассмотреть пример стейта на статьях в блоге))
Ну за это, я ЗА!)
Dukobpa3 Спасибо Вам за статьи! Скоро начну их все читать, пока время нет
 

 


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


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