- Состояние (шаблон проектирования)
-
Шаблон проектирования Состояние State Тип: поведенческий
Описан в Design Patterns Да
Состояние (англ. State) — шаблон проектирования. Используется в тех случаях, когда во время выполнения программы объект должен менять свое поведение в зависимости от своего состояния.
Паттерн состоит из 3 блоков:
Widget — класс, объекты которого должны менять свое поведение в зависимости от состояния.
IState — интерфейс, который должен реализовать каждое из конкретных состояний. Через этот интерфейс объект Widget взаимодействует с состоянием, делегируя ему вызовы методов. Интерфейс должен содержать средства для обратной связи с объектом, поведение которого нужно изменить. Для этого используется событие (паттерн Publisher — Subscriber). Это необходимо для того, чтобы в процессе выполнения программы заменять объект состояния при появлении событий. Возможны случаи, когда сам Widget периодически опрашивает объект состояние на наличие перехода.
StateA … StateZ — классы конкретных состояний. Должны содержать информацию о том, при каких условиях и в какие состояния может переходить объект из текущего состояния. Например, из StateA объект может переходить в состояние StateB и StateC, а из StateB — обратно в StateA и так далее. Объект одного из них должен содержать Widget при создании.
Содержание
Примеры
Пример на C#
Применение шаблона
Исходный текст на языке csharpusing System; namespace Digital_Patterns.Behavioral.State { public interface IAutomatState { String GotApplication(); String CheckApplication(); String RentApartment(); String DispenseKeys(); } public interface IAutomat { void GotApplication(); void CheckApplication(); void RentApartment(); void SetState(IAutomatState s); IAutomatState GetWaitingState(); IAutomatState GetGotApplicationState(); IAutomatState GetApartmentRentedState(); IAutomatState GetFullyRentedState(); Int32 Count { get; set; } } public class Automat : IAutomat { private IAutomatState _waitingState; private IAutomatState _gotApplicationState; private IAutomatState _apartmentRentedState; private IAutomatState _fullyRentedState; private IAutomatState _state; private Int32 _count; public Automat(Int32 n) { _count = n; _waitingState = new WaitingState(this); _gotApplicationState = new GotApplicationState(this); _apartmentRentedState = new ApartmentRentedState(this); _fullyRentedState = new FullyRentedState(this); _state = _waitingState; } public void GotApplication() { Console.WriteLine(_state.GotApplication()); } public void CheckApplication() { Console.WriteLine(_state.CheckApplication()); } public void RentApartment() { Console.WriteLine(_state.RentApartment()); Console.WriteLine(_state.DispenseKeys()); } public void SetState(IAutomatState s) { _state = s; } public IAutomatState GetWaitingState() { return _waitingState; } public IAutomatState GetGotApplicationState() { return _gotApplicationState; } public IAutomatState GetApartmentRentedState() { return _apartmentRentedState; } public IAutomatState GetFullyRentedState() { return _fullyRentedState; } public int Count { get { return _count; } set { _count = value; } } } public class WaitingState : IAutomatState { private Automat _automat; public WaitingState(Automat automat) { _automat = automat; } public String GotApplication() { _automat.SetState(_automat.GetGotApplicationState()); return "Thanks for the application."; } public String CheckApplication() { return "You have to submit an application."; } public String RentApartment() { return "You have to submit an application."; } public String DispenseKeys() { return "You have to submit an application."; } } public class GotApplicationState : IAutomatState { private Automat _automat; private readonly Random _random; public GotApplicationState(Automat automat) { _automat = automat; _random = new Random(System.DateTime.Now.Millisecond); } public String GotApplication() { return "We already got your application."; } public String CheckApplication() { var yesNo = _random.Next() % 10; if (yesNo > 4 && _automat.Count > 0) { _automat.SetState(_automat.GetApartmentRentedState()); return "Congratulations, you were approved."; } else { _automat.SetState(_automat.GetWaitingState()); return "Sorry, you were not approved."; } } public String RentApartment() { return "You must have your application checked."; } public String DispenseKeys() { return "You must have your application checked."; } } public class ApartmentRentedState : IAutomatState { private Automat _automat; public ApartmentRentedState(Automat automat) { _automat = automat; } public String GotApplication() { return "Hang on, we'ra renting you an apartmeny."; } public String CheckApplication() { return "Hang on, we'ra renting you an apartmeny."; } public String RentApartment() { _automat.Count = _automat.Count - 1; return "Renting you an apartment...."; } public String DispenseKeys() { if(_automat.Count <= 0) _automat.SetState(_automat.GetFullyRentedState()); else _automat.SetState(_automat.GetWaitingState()); return "Here are your keys!"; } } public class FullyRentedState : IAutomatState { private Automat _automat; public FullyRentedState(Automat automat) { _automat = automat; } public String GotApplication() { return "Sorry, we're fully rented."; } public String CheckApplication() { return "Sorry, we're fully rented."; } public String RentApartment() { return "Sorry, we're fully rented."; } public String DispenseKeys() { return "Sorry, we're fully rented."; } } class Program { static void Main(string[] args) { var automat = new Automat(9); automat.GotApplication(); automat.CheckApplication(); automat.RentApartment(); } } }
Тот же пример, без применения шаблона
Исходный текст на языке csharpusing System; namespace Digital_Patterns.Behavioral.State { public enum State { FULLY_RENTED = 0, WAITING = 1, GOT_APPLICATION = 2, APARTMENT_RENTED = 3, } public class RentalMethods { private readonly Random _random; private Int32 _numberApartments; private State _state = State.WAITING; public RentalMethods(Int32 n) { _numberApartments = n; _random = new Random(System.DateTime.Now.Millisecond); } public void GetApplication() { switch (_state) { case State.FULLY_RENTED: Console.WriteLine("Sorry, we're fully rented."); break; case State.WAITING: _state = State.GOT_APPLICATION; Console.WriteLine("Thanks for the application."); break; case State.GOT_APPLICATION: Console.WriteLine("We already got your application."); break; case State.APARTMENT_RENTED: Console.WriteLine("Hang on, we'ra renting you an apartmeny."); break; } } public void CheckApplication() { var yesNo = _random.Next()%10; switch (_state) { case State.FULLY_RENTED: Console.WriteLine("Sorry, we're fully rented."); break; case State.WAITING: Console.WriteLine("You have to submit an application."); break; case State.GOT_APPLICATION: if (yesNo > 4 && _numberApartments > 0) { Console.WriteLine("Congratulations, you were approved."); _state = State.APARTMENT_RENTED; RentApartment(); } else { Console.WriteLine("Sorry, you were not approved."); _state = State.WAITING; } break; case State.APARTMENT_RENTED: Console.WriteLine("Hang on, we'ra renting you an apartmeny."); break; } } public void RentApartment() { switch (_state) { case State.FULLY_RENTED: Console.WriteLine("Sorry, we're fully rented."); break; case State.WAITING: Console.WriteLine("You have to submit an application."); break; case State.GOT_APPLICATION: Console.WriteLine("You must have your application checked."); break; case State.APARTMENT_RENTED: Console.WriteLine("Renting you an apartment...."); _numberApartments--; DispenseKeys(); break; } } public void DispenseKeys() { switch (_state) { case State.FULLY_RENTED: Console.WriteLine("Sorry, we're fully rented."); break; case State.WAITING: Console.WriteLine("You have to submit an application."); break; case State.GOT_APPLICATION: Console.WriteLine("You must have your application checked."); break; case State.APARTMENT_RENTED: Console.WriteLine("Here are your keys!"); _state = State.WAITING; break; } } } class Program { static void Main(string[] args) { var rentalMethods = new RentalMethods(9); rentalMethods.GetApplication(); rentalMethods.CheckApplication(); rentalMethods.RentApartment(); rentalMethods.DispenseKeys(); } } }
Пример на Javascript
Пример со сменой состояний из State.
Исходный текст на языке javascript// "интерфейс" State function State() { this.someMethod = function() { }; this.nextState = function() { }; } // реализация State // первое состояние function StateA(widjet) { var dublicate = this; // ссылка на инстанцирующийся объект (т.к. this может меняться) this.someMethod = function() { alert("StateA.someMethod"); dublicate.nextState(); }; this.nextState = function() { alert("StateA > StateB"); widjet.onNextState( new StateB(widjet) ); }; } StateA.prototype = new State(); StateA.prototype.constructor = StateA; // второе состояние function StateB(widjet) { var dublicate = this; this.someMethod = function() { alert("StateB.someMethod"); dublicate.nextState(); }; this.nextState = function() { alert("StateB > StateA"); widjet.onNextState( new StateA(widjet) ); }; } StateB.prototype = new State(); StateB.prototype.constructor = StateB; // "интерфейс" Widget function Widget() { this.someMethod = function() { }; this.onNextState = function(state) { }; } // реализация Widget function Widget1() { var state = new StateA(this); this.someMethod = function() { state.someMethod(); }; this.onNextState = function(newState) { state = newState; }; } Widget1.prototype = new Widget(); Widget1.prototype.constructor = Widget1; // использование var widget = new Widget1(); widget.someMethod(); // StateA.someMethod // StateA > StateB widget.someMethod(); // StateB.someMethod // StateB > StateA
Смена состояний с помощью вызова метода у Widget (из англоязычной версии статьи).
Исходный текст на языке javascript// "интерфейс" State function AbstractTool() { this.moveTo = function(x, y) { }; this.mouseDown = function(x, y) { }; this.mouseUp = function(x, y) { }; } // реализация State // инструмент "карандаш" function PenTool(widjet) { var dublicate = this; // ссылка на инстанцирующийся объект (т.к. this может меняться) var mouseIsDown = false; // кнопка мыши сейчас не нажата var lastCoords = []; // прошлые координаты курсора мыши this.moveTo = function(x, y) { if (mouseIsDown && lastCoords.length) { drawLine(lastCoords, [x, y]); } lastCoords = [x, y]; }; this.mouseDown = function(x, y) { mouseIsDown = true; lastCoords = [x, y]; }; this.mouseUp = function(x, y) { mouseIsDown = false; }; function drawLine(coords1, coords2) { alert("drawLine: ["+ coords1[0] +", "+ coords1[1] +"] - ["+ coords2[0] +", "+ coords2[1] +"]"); } } PenTool.prototype = new AbstractTool(); PenTool.prototype.constructor = PenTool; // инструмент "выделение области" function SelectionTool(widget) { var dublicate = this; // ссылка на инстанцирующийся объект (т.к. this может меняться) var mouseIsDown = false; // кнопка мыши сейчас не нажата var startCoords = []; // координаты курсора мыши при нажатии на кнопку this.moveTo = function(x, y) { if (mouseIsDown) { setSelection(startCoords, [x, y]); } }; this.mouseDown = function(x, y) { startCoords = [x, y]; mouseIsDown = true; }; this.mouseUp = function(x, y) { mouseIsDown = false; }; function setSelection(coords1, coords2) { alert("setSelection: ["+ coords1[0] +", "+ coords1[1] +"] - ["+ coords2[0] +", "+ coords2[1] +"]"); } }; SelectionTool.prototype = new AbstractTool(); SelectionTool.prototype.constructor = SelectionTool; // реализация Widget function DrawingController() { var currentTool = new SelectionTool(); // активный инструмент this.moveTo = function(x, y) { currentTool.moveTo(x, y); }; this.mouseDown = function(x, y) { currentTool.mouseDown(x, y); }; this.mouseUp = function(x, y) { currentTool.mouseUp(x, y); }; this.selectPenTool = function() { // выбираем инструмент "выделение области" currentTool = new PenTool(); }; this.selectSelectionTool = function() { // выбираем инструмент "карандаш" currentTool = new SelectionTool(); }; } var widget = new DrawingController(); widget.mouseDown(1, 1); widget.moveTo(1, 2); // setSelection: [1, 1] - [1, 2] widget.moveTo(1, 3); // setSelection: [1, 1] - [1, 3] widget.mouseUp(1, 3); widget.moveTo(1, 4); widget.selectPenTool(); widget.mouseDown(1, 1); widget.moveTo(1, 2); // drawLine: [1, 1] - [1, 2] widget.moveTo(1, 3); // drawLine: [1, 2] - [1, 3] widget.mouseUp(1, 3); widget.moveTo(1, 4);
Пример на CoffeeScript
Исходный текст на языке CoffeeScript# Абстрактный класс class Tool mouseUp : -> mouseDown : -> mouseMove : -> # Конкретные состояния class PenTool extends Tool mouseDown : (e) -> @start = x : e.coors.x y : e.coors.y mouseUp : (e) -> new Shapes.Line(@start.x, @start.y, e.coors.x, e.coors.y) class ZoomTool extends Tool maxZoom = 5 minZoom = 0.5 delta = 0.5 constructor : -> @zoom = 1 mouseDown : (e) -> if e.shiftKey @zoom -= delta unless @zoom - minZoom < delta else @zoom += delta unless maxZoom - @zoom < delta # Виджет class DrawingController # private getCoords = (elem, event) -> box = elem.getBoundingClientRect() x : Math.round(event.clientX - box.left) y : Math.round(event.clientY - box.top) # public constructor : (@canvas) -> @penTool = new PenTool @zoomTool = new ZoomTool @currentTool = @penTool # default tool # Фабрика обработчиков событий мыши handler = (type) => (e) => e.coors = getCoords(@canvas, e) @currentTool['mouse'+type](e) e.preventDefault() # Вешаем обработчики canvas.addEventListener('mouseup' , handler( 'Up' ), off) canvas.addEventListener('mousedown', handler('Down'), off) canvas.addEventListener('mousemove', handler('Move'), off) selectPenTool : -> @currentTool = @penTool selectZoomTool : -> @currentTool = @zoomTool
Пример на VB.NET
Применение шаблона
Исходный текст на языке VB.NETNamespace Digital_Patterns.Behavioral.State Public Interface IAutomatState Function GotApplication() As [String] Function CheckApplication() As [String] Function RentApartment() As [String] Function DispenseKeys() As [String] End Interface Public Interface IAutomat Sub GotApplication() Sub CheckApplication() Sub RentApartment() Sub SetState(ByVal s As IAutomatState) Function GetWaitingState() As IAutomatState Function GetGotApplicationState() As IAutomatState Function GetApartmentRentedState() As IAutomatState Function GetFullyRentedState() As IAutomatState Property Count() As Int32 End Interface Public Class Automat Implements IAutomat Private _waitingState As IAutomatState Private _gotApplicationState As IAutomatState Private _apartmentRentedState As IAutomatState Private _fullyRentedState As IAutomatState Private _state As IAutomatState Private _count As Int32 Public Sub New(ByVal n As Int32) _count = n _waitingState = New WaitingState(Me) _gotApplicationState = New GotApplicationState(Me) _apartmentRentedState = New ApartmentRentedState(Me) _fullyRentedState = New FullyRentedState(Me) _state = _waitingState End Sub Public Sub GotApplication() Implements IAutomat.GotApplication Console.WriteLine(_state.GotApplication()) End Sub Public Sub CheckApplication() Implements IAutomat.CheckApplication Console.WriteLine(_state.CheckApplication()) End Sub Public Sub RentApartment() Implements IAutomat.RentApartment Console.WriteLine(_state.RentApartment()) Console.WriteLine(_state.DispenseKeys()) End Sub Public Sub SetState(ByVal s As IAutomatState) Implements IAutomat.SetState _state = s End Sub Public Function GetWaitingState() As IAutomatState Implements IAutomat.GetWaitingState Return _waitingState End Function Public Function GetGotApplicationState() As IAutomatState Implements IAutomat.GetGotApplicationState Return _gotApplicationState End Function Public Function GetApartmentRentedState() As IAutomatState Implements IAutomat.GetApartmentRentedState Return _apartmentRentedState End Function Public Function GetFullyRentedState() As IAutomatState Implements IAutomat.GetFullyRentedState Return _fullyRentedState End Function Public Property Count() As Integer Implements IAutomat.Count Get Return _count End Get Set(ByVal value As Integer) _count = value End Set End Property End Class Public Class WaitingState Implements IAutomatState Private _automat As Automat Public Sub New(ByVal automat As Automat) _automat = automat End Sub Public Function GotApplication() As [String] Implements IAutomatState.GotApplication _automat.SetState(_automat.GetGotApplicationState()) Return "Thanks for the application." End Function Public Function CheckApplication() As [String] Implements IAutomatState.CheckApplication Return "You have to submit an application." End Function Public Function RentApartment() As [String] Implements IAutomatState.RentApartment Return "You have to submit an application." End Function Public Function DispenseKeys() As [String] Implements IAutomatState.DispenseKeys Return "You have to submit an application." End Function End Class Public Class GotApplicationState Implements IAutomatState Private _automat As Automat Private ReadOnly _random As Random Public Sub New(ByVal automat As Automat) _automat = automat _random = New Random(System.DateTime.Now.Millisecond) End Sub Public Function GotApplication() As [String] Implements IAutomatState.GotApplication Return "We already got your application." End Function Public Function CheckApplication() As [String] Implements IAutomatState.CheckApplication Dim yesNo = _random.[Next]() Mod 10 If yesNo > 4 AndAlso _automat.Count > 0 Then _automat.SetState(_automat.GetApartmentRentedState()) Return "Congratulations, you were approved." Else _automat.SetState(_automat.GetWaitingState()) Return "Sorry, you were not approved." End If End Function Public Function RentApartment() As [String] Implements IAutomatState.RentApartment Return "You must have your application checked." End Function Public Function DispenseKeys() As [String] Implements IAutomatState.DispenseKeys Return "You must have your application checked." End Function End Class Public Class ApartmentRentedState Implements IAutomatState Private _automat As Automat Public Sub New(ByVal automat As Automat) _automat = automat End Sub Public Function GotApplication() As [String] Implements IAutomatState.GotApplication Return "Hang on, we'ra renting you an apartmeny." End Function Public Function CheckApplication() As [String] Implements IAutomatState.CheckApplication Return "Hang on, we'ra renting you an apartmeny." End Function Public Function RentApartment() As [String] Implements IAutomatState.RentApartment _automat.Count = _automat.Count - 1 Return "Renting you an apartment...." End Function Public Function DispenseKeys() As [String] Implements IAutomatState.DispenseKeys If _automat.Count <= 0 Then _automat.SetState(_automat.GetFullyRentedState()) Else _automat.SetState(_automat.GetWaitingState()) End If Return "Here are your keys!" End Function End Class Public Class FullyRentedState Implements IAutomatState Private _automat As Automat Public Sub New(ByVal automat As Automat) _automat = automat End Sub Public Function GotApplication() As [String] Implements IAutomatState.GotApplication Return "Sorry, we're fully rented." End Function Public Function CheckApplication() As [String] Implements IAutomatState.CheckApplication Return "Sorry, we're fully rented." End Function Public Function RentApartment() As [String] Implements IAutomatState.RentApartment Return "Sorry, we're fully rented." End Function Public Function DispenseKeys() As [String] Implements IAutomatState.DispenseKeys Return "Sorry, we're fully rented." End Function End Class Class Program Shared Sub Main() Dim automat = New Automat(9) automat.GotApplication() automat.CheckApplication() automat.RentApartment() Console.Read() End Sub End Class End Namespace
Тот же пример, без применения шаблона
Исходный текст на языке VB.NETNamespace Digital_Patterns.Behavioral.State Public Enum State FULLY_RENTED = 0 WAITING = 1 GOT_APPLICATION = 2 APARTMENT_RENTED = 3 End Enum Public Class RentalMethods Private ReadOnly _random As Random Private _numberApartments As Int32 Private _state As State = State.WAITING Public Sub New(ByVal n As Int32) _numberApartments = n _random = New Random(System.DateTime.Now.Millisecond) End Sub Public Sub GetApplication() Select Case _state Case State.FULLY_RENTED Console.WriteLine("Sorry, we're fully rented.") Exit Select Case State.WAITING _state = State.GOT_APPLICATION Console.WriteLine("Thanks for the application.") Exit Select Case State.GOT_APPLICATION Console.WriteLine("We already got your application.") Exit Select Case State.APARTMENT_RENTED Console.WriteLine("Hang on, we'ra renting you an apartmeny.") Exit Select End Select End Sub Public Sub CheckApplication() Dim yesNo = _random.[Next]() Mod 10 Select Case _state Case State.FULLY_RENTED Console.WriteLine("Sorry, we're fully rented.") Exit Select Case State.WAITING Console.WriteLine("You have to submit an application.") Exit Select Case State.GOT_APPLICATION If yesNo > 4 AndAlso _numberApartments > 0 Then Console.WriteLine("Congratulations, you were approved.") _state = State.APARTMENT_RENTED RentApartment() Else Console.WriteLine("Sorry, you were not approved.") _state = State.WAITING End If Exit Select Case State.APARTMENT_RENTED Console.WriteLine("Hang on, we'ra renting you an apartmeny.") Exit Select End Select End Sub Public Sub RentApartment() Select Case _state Case State.FULLY_RENTED Console.WriteLine("Sorry, we're fully rented.") Exit Select Case State.WAITING Console.WriteLine("You have to submit an application.") Exit Select Case State.GOT_APPLICATION Console.WriteLine("You must have your application checked.") Exit Select Case State.APARTMENT_RENTED Console.WriteLine("Renting you an apartment....") _numberApartments -= 1 DispenseKeys() Exit Select End Select End Sub Public Sub DispenseKeys() Select Case _state Case State.FULLY_RENTED Console.WriteLine("Sorry, we're fully rented.") Exit Select Case State.WAITING Console.WriteLine("You have to submit an application.") Exit Select Case State.GOT_APPLICATION Console.WriteLine("You must have your application checked.") Exit Select Case State.APARTMENT_RENTED Console.WriteLine("Here are your keys!") _state = State.WAITING Exit Select End Select End Sub End Class Class Program Shared Sub Main() Dim rentalMethods = New RentalMethods(9) rentalMethods.GetApplication() rentalMethods.CheckApplication() rentalMethods.RentApartment() rentalMethods.DispenseKeys() Console.Read() End Sub End Class End Namespace
Пример на PHP5
Исходный текст на языке php5<?php /** * Паттерн Состояние управляет изменением поведения объекта при изменении его внутреннего состояния. * Внешне это выглядит так, словно объект меняет свой класс. */ namespace state1 { class Client { public function __construct() { $context = new Context(); $context->request(); $context->request(); $context->request(); $context->request(); $context->request(); $context->request(); } } class Test { public static function go() { $client = new Client(); } } /** * Класс с несколькими внутренними состояниями */ class Context { /** * @var AState */ public $state; const STATE_A = 1; const STATE_B = 2; const STATE_C = 3; public function __construct() { $this->setState(Context::STATE_A); } /** * Действия Context делегируются объектам состояний для обработки */ public function request() { $this->state->handle(); } /** * Это один из способов реализации переключения состояний * @param $state выбранное состояние, возможные варианты перечислены в списке констант Context::STATE_.. */ public function setState($state) { if ($state == Context::STATE_A) { $this->state = new ConcreteStateA($this); } elseif ($state == Context::STATE_B) { $this->state = new ConcreteStateB($this); } elseif ($state == Context::STATE_C) { $this->state = new ConcreteStateC($this); } } } /** * Общий интерфейс всех конкретных состояний. * Все состояния реализуют один интерфейс, а следовтельно, являются взаимозаменяемыми. */ class AState { /** * @var Context храним ссылку на контекст для удобного переключения состояний */ protected $context; public function __construct(&$context) { $this->context =& $context; } /** * Обработка в разных состояниях может отличаться. * Если AState не просто интерфейс а абстрактный класс, * то он может содержать стандартные обработки, тогда классы конкретных состояний будут описывать только свои особенности относительно стандартного поведения. */ public function handle() { echo "\n standart handle"; } } /** * Далее идёт набор конкретных состояний, которые обрабатывают запросы от Context. * Каждый класс предоставляет собственную реализацию запроса. * Таким образом, при переходе объекта Context в другое состояние, меняется и его повденеие. */ class ConcreteStateA extends AState { public function handle() { echo "\n State A handle"; // переключаем состояние $this->context->setState(Context::STATE_B); } } class ConcreteStateB extends AState { public function handle() { echo "\n State B handle"; // переключаем состояние $this->context->setState(Context::STATE_C); } } class ConcreteStateC extends AState { public function handle() { echo "\n State C handle"; // переключаем состояние $this->context->setState(Context::STATE_A); } } Test::go(); }
Пример с автоматом из head first patterns на языке php5<?php /** * Более конкретный пример на основе паттерна "Состояние". * Паттерн Состояние управляет изменением поведения объекта при изменении его внутреннего состояния. * Внешне это выглядит так, словно объект меняет свой класс. */ namespace state2 { class Client { public function __construct() { $context = new Context(); $context->dispense(); $context->insertQuarter(); $context->turnCrank(); $context->insertQuarter(); $context->turnCrank(); $context->insertQuarter(); $context->turnCrank(); } } class Test { public static function go() { $client = new Client(); } } /** * Класс с несколькими внутренними состояниями */ class Context { /** * @var AState */ public $state; /** * Возможные состояния */ const STATE_SOLD_OUT = 1; const STATE_NO_QUARTER_STATE = 2; const STATE_HAS_QUARTER_STATE = 3; const STATE_SOLD_STATE = 4; const STATE_WINNER_STATE = 5; /** * @var int Сколько жвачки в автомате? */ public $count = 2; public function __construct() { $this->setState(Context::STATE_NO_QUARTER_STATE); } /** * Действия Context делегируются объектам состояний для обработки */ public function insertQuarter() { $this->state->insertQuarter(); } public function ejectQuarter() { $this->state->ejectQuarter(); } public function turnCrank() { $this->state->turnCrank(); $this->state->dispense(); } public function dispense() { $this->state->dispense(); } /** * Это один из способов реализации переключения состояний * @param $state выбранное состояние, возможные варианты перечислены в списке констант Context::STATE_.. */ public function setState($state) { if ($state == Context::STATE_SOLD_OUT) { $this->state = new ConcreteStateSoldOut($this); } elseif ($state == Context::STATE_NO_QUARTER_STATE) { $this->state = new ConcreteStateNoQuarter($this); } elseif ($state == Context::STATE_HAS_QUARTER_STATE) { $this->state = new ConcreteStateHasQuarter($this); } elseif ($state == Context::STATE_SOLD_STATE) { $this->state = new ConcreteStateSoldState($this); } elseif ($state == Context::STATE_WINNER_STATE) { $this->state = new ConcreteStateWinnerState($this); } } public function releaseBall() { if ($this->count > 0) { echo "Ball released"; $this->count -= 1; } else { echo "No balls to release :("; } } } /** * Общий интерфейс всех конкретных состояний. * Все состояния реализуют один интерфейс, а следовтельно, являются взаимозаменяемыми. */ class AState { /** * @var Context храним ссылку на контекст для удобного переключения состояний */ protected $context; public function __construct(&$context) { $this->context =& $context; } /** * Обработка в разных состояниях может отличаться. * Если AState не просто интерфейс а абстрактный класс, * то он может содержать стандартные обработки, тогда классы конкретных состояний будут описывать только свои особенности относительно стандартного поведения. */ public function insertQuarter() { echo "\n lol, you can't do that"; } public function ejectQuarter() { echo "\n lol, you can't do that"; } public function turnCrank() { echo "\n lol, you can't do that"; } public function dispense() { echo "\n lol, you can't do that"; } } /** * Далее идёт набор конкретных состояний, которые обрабатывают запросы от Context. * Каждый класс предоставляет собственную реализацию запроса. * Таким образом, при переходе объекта Context в другое состояние, меняется и его повденеие. */ class ConcreteStateSoldOut extends AState { public function insertQuarter() { echo "\n sorry, i'm sold out, can't take quarters"; } } class ConcreteStateNoQuarter extends AState { public function insertQuarter() { echo "\n got quarter, yeah!"; // переключаем состояние $this->context->setState(Context::STATE_HAS_QUARTER_STATE); } } class ConcreteStateHasQuarter extends AState { public function ejectQuarter() { echo "\n take your money back"; // переключаем состояние $this->context->setState(Context::STATE_NO_QUARTER_STATE); } public function turnCrank() { echo "\n you turned"; $winner = rand(1, 10) == 10 ? 1 : 0; if ($winner) { $this->context->setState(Context::STATE_WINNER_STATE); } else { $this->context->setState(Context::STATE_SOLD_STATE); } } } class ConcreteStateSoldState extends AState { public function dispense() { echo "\n dispensing, yeah!"; $this->context->releaseBall(); if ($this->context->count == 0) { $this->context->setState(Context::STATE_SOLD_OUT); } else { // переключаем состояние $this->context->setState(Context::STATE_NO_QUARTER_STATE); } } } class ConcreteStateWinnerState extends AState { public function dispense() { echo "\n dispensing, yeah!"; $this->context->releaseBall(); if ($this->context->count == 0) { $this->context->setState(Context::STATE_SOLD_OUT); } else { echo "\n p.s. you are WINNER, you get extra ball!"; $this->context->releaseBall(); if ($this->context->count == 0) { $this->context->setState(Context::STATE_SOLD_OUT); } else { $this->context->setState(Context::STATE_NO_QUARTER_STATE); } } } } Test::go(); }
Шаблоны проектирования Основные Порождающие Структурные Поведенческие Интерпретатор • Итератор • Команда • Наблюдатель • Посетитель • Посредник • Состояние • Стратегия • Хранитель • Цепочка обязанностей • Шаблонный метод
Блокировка с двойной проверкой • Однопоточное выполнение • Планировщик У этой статьи нет иллюстраций. Вы можете помочь проекту, добавив их (с соблюдением правил использования изображений).
Для поиска иллюстраций можно:- попробовать воспользоваться инструментом FIST: нажмите эту ссылку, чтобы начать поиск;
- попытаться найти изображение на Викискладе;
- просмотреть иноязычные варианты статьи (если они есть);
- см. также Википедия:Источники изображений.
Категория:- Шаблоны проектирования
Wikimedia Foundation. 2010.