State (Состояние)
Опять же для затравочки.
Ссылка на годный плюсовый пример(зауважал этот сайт в последнее время, хорошо пишут)
Примеры разных паттернов
Пример реализации стейта
Видео от этого чувака по паттернам
Поехали.
Опираться буду на пример паттернкрафта. Он вполне достойный.
Итак, в предыдущей статье мы рассмотрели паттерн стейт-машины. Он нам нужен если у объекта есть несколько состояний, которые меняют поведение объекта.
Но мы рассмотрели примитивный пример, который затрагивает только два параметра: скорость и атаку. Такие варианты стейт-машин целесообразно решать именно так как я там описал. Простейший свитч и погнали.
Но рассмотрим ситуацию когда меняется не только парочка геттеров а и кусок поведения.
Цитата:
Паттерн State решает указанную проблему следующим образом:
- Вводит класс Context, в котором определяется интерфейс для внешнего мира.
- Вводит абстрактный класс State.
- Представляет различные "состояния" конечного автомата в виде подклассов State.
- В классе Context имеется указатель на текущее состояние, который изменяется при изменении состояния конечного автомата.
- Вводит класс Context, в котором определяется интерфейс для внешнего мира.
- Вводит абстрактный класс State.
- Представляет различные "состояния" конечного автомата в виде подклассов State.
- В классе Context имеется указатель на текущее состояние, который изменяется при изменении состояния конечного автомата.
Пишем несколько стейтов, которые имплементят этот интерфейс.
Даем этим объектам-стейтам ссылку на наш главный объект. Реализацию поведения главного объекта выносим из самого объекта в стейты.
Таким образом наш главный объект становится как бы прокси над текущим стейтом, а сам ничего делать не умеет.
Так же нам нужен некий контекст. Штука, которая занимается переключением стейтов. Можно контекст вшить в сам главный объект, а можем сделать как отдельной сущностью.
Ниже я буду описывать свою реализацию, за основу беру паттернкрафт и буду объяснять свои изменения. Предполагаю что код паттернкрафт вы смотрели.
Во-первых, мы таки добавим отдельный класс контекста. Следуя принципам single responsibility
- пусть танк решает задачи танка - ездит и стреляет.
- Контекст будет переключать нужное состояние.
- Стейт будет выдавать некий интерфейс с уникальной реализацией поведения для каждого состояния танка.
Итого из танка убрал механизмы переключения стейтов, это управление передается контексту:
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); } } }
В его задачи входит контролировать возможность перехода из одного стейта в другой и реализовывать сам механизм перехода.
Принимает в конструктор ссылку на танк, но себе ее не сохраняет, только отдает стейтам, так как рулить танком - задача стейтов.
Выдает наружу геттер текущего стейта.
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]; } } }
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; } }
Проигнорировал то что могут быть разные анимации перехода из состояния танка в скоростной режим и в режим осады. Сделал один таймер. Но это вроде не проблема.
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
Комментарии
![]() ![]() |
|
Апдейтил текст.
|
![]() ![]() |
|
Кстати, можно реальный пример на редактировании записи в блоге тут. Если ты блогер одни состояния записи, если нет - другие. ;-)
|
![]() ![]() |
|
GBee хочу уточнить.. Вы предлагаете прятать часть статей от тех, кто не ведет блогов??)
|
![]() ![]() |
|
Akopalipsis
GBee предлагает рассмотреть пример стейта на статьях в блоге)) |
![]() ![]() |
|
Цитата:
GBee предлагает рассмотреть пример стейта на статьях в блоге))
Dukobpa3 Спасибо Вам за статьи! Скоро начну их все читать, пока время нет ![]() |
Последние записи от Dukobpa3
- Strategy (Стратегия) (27.12.2013)
- State (Состояние) (27.12.2013)
- State-machine (конечный автомат, машина состояний) (25.12.2013)
- Медиатор, Прокси (14.11.2013)
- Инкапсуляция объекта vs инкапсуляция поведения (14.11.2013)