Состояние (шаблон проектирования)
Из Википедии, бесплатной энциклопедии
Состояние | |
---|---|
State | |
Тип | поведенческий |
Описан в Design Patterns | Да |
Состояние (англ. State) — поведенческий шаблон проектирования. Используется в тех случаях, когда во время выполнения программы объект должен менять своё поведение в зависимости от своего состояния.
Паттерн состоит из 3 блоков:
Widget — класс, объекты которого должны менять своё поведение в зависимости от состояния.
IState — интерфейс, который должен реализовать каждое из конкретных состояний. Через этот интерфейс объект Widget взаимодействует с состоянием, делегируя ему вызовы методов. Интерфейс должен содержать средства для обратной связи с объектом, поведение которого нужно изменить. Для этого используется событие (паттерн Publisher — Subscriber). Это необходимо для того, чтобы в процессе выполнения программы заменять объект состояния при появлении событий. Возможны случаи, когда сам Widget периодически опрашивает объект состояния на наличие перехода.
StateA … StateZ — классы конкретных состояний. Должны содержать информацию о том, при каких условиях и в какие состояния может переходить объект из текущего состояния. Например, из StateA объект может переходить в состояние StateB и StateC, а из StateB — обратно в StateA и так далее. Объект одного из них должен содержать Widget при создании.
Применение данного паттерна может быть затруднено, если состояния должны обмениваться данными, или одно состояние настраивает свойства другого. В этом случае понадобится глобальный объект, что не очень хорошее архитектурное решение.
Примеры
[править | править код]Пример на C++
[править | править код]#pragma once #include<string> #include<iostream> class State; class StateContext; class SolidState; class LiquidState; class GasState; class State { std::string name; public: State(const std::string& name) : name(name) {} std::string GetName() { return name; } virtual void Freeze(StateContext*) = 0; virtual void Heat(StateContext*) = 0; }; class StateContext { private: State* state; public: StateContext(State* state) : state(state) {} void Freeze() { std::cout << "Freezing " << state->GetName() << "..." << std::endl; state->Freeze(this); } void Heat() { std::cout << "Heating " << state->GetName() << "..." << std::endl; state->Heat(this); } void SetState(State* s) { std::cout << "Changing state from " << state->GetName() << " to " << s->GetName() << "..." << std::endl; delete state; state = s; } State* GetState() { return state; } ~StateContext() { delete state; } }; class SolidState : public State { public: SolidState() : State("Solid") {} virtual void Freeze(StateContext* state); virtual void Heat(StateContext* state); }; class LiquidState : public State { public: LiquidState() : State("Liquid") {} virtual void Freeze(StateContext* state); virtual void Heat(StateContext* state); }; class GasState : public State { public: GasState() : State("Gas") {} virtual void Freeze(StateContext* state); virtual void Heat(StateContext* state); }; void SolidState::Freeze(StateContext* state) { std::cout << "Nothing happens" << std::endl; } void SolidState::Heat(StateContext* state) { state->SetState(new LiquidState()); } void LiquidState::Freeze(StateContext* state) { state->SetState(new SolidState()); } void LiquidState::Heat(StateContext* state) { state->SetState(new GasState()); } void GasState::Freeze(StateContext* state) { state->SetState(new LiquidState()); } void GasState::Heat(StateContext* state) { std::cout << "Nothing happens" << std::endl; } void Test() { StateContext* sc = new StateContext(new SolidState()); sc->Heat(); sc->Heat(); sc->Heat(); sc->Freeze(); sc->Freeze(); sc->Freeze(); delete sc; }
Пример на C#
[править | править код]Применение шаблона
using 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(); } } }
Тот же пример, без применения шаблона
using 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 apartment."); 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 apartment."); 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(); } } }
Пример на Java
[править | править код]public class StateExample { public static void main(String[] args) { StateContext context = new StateContext(); context.heat(); context.heat(); context.heat(); context.freeze(); context.freeze(); context.freeze(); // OUTPUT: // Heating solid substance... // Changing state to liquid... // Heating liquid substance... // Changing state to gaseous... // Heating gaseous substance... // Nothing happens. // Freezing gaseous substance... // Changing state to liquid... // Freezing liquid substance... // Changing state to solid... // Freezing solid substance... // Nothing happens. } } interface State { String getName(); void freeze(StateContext context); void heat(StateContext context); } class SolidState implements State { private static final String NAME = "solid"; public String getName() { return NAME; } public void freeze(StateContext context) { System.out.println("Nothing happens."); } public void heat(StateContext context) { context.setState(new LiquidState()); } } class LiquidState implements State { private static final String NAME = "liquid"; public String getName() { return NAME; } public void freeze(StateContext context) { context.setState(new SolidState()); } public void heat(StateContext context) { context.setState(new GaseousState()); } } class GaseousState implements State { private static final String NAME = "gaseous"; public String getName() { return NAME; } public void freeze(StateContext context) { context.setState(new LiquidState()); } public void heat(StateContext context) { System.out.println("Nothing happens."); } } class StateContext { private State state = new SolidState(); public void freeze() { System.out.println("Freezing " + state.getName() + " substance..."); state.freeze(this); } public void heat() { System.out.println("Heating " + state.getName() + " substance..."); state.heat(this); } public void setState(State state) { System.out.println("Changing state to " + state.getName() + "..."); this.state = state; } public State getState() { return state; } }
Пример на Python
[править | править код]from abc import ABCMeta, abstractmethod class State(metaclass=ABCMeta): @abstractmethod def eat(self) -> str: pass @abstractmethod def find_food(self) -> str: pass @abstractmethod def move(self) -> str: pass @abstractmethod def dream(self) -> str: pass class SleepState(State): def eat(self) -> str: return 'не может есть, пока спит' def find_food(self) -> str: return 'ищет еду, но только в своих мечтах' def move(self) -> str: return 'не может двигаться, пока спит' def dream(self) -> str: return 'спит и видит чудный сон' class OnGroundState(State): def eat(self) -> str: return 'вываливает на пузо добытых моллюсков и начинает неспешно их есть' def find_food(self) -> str: return 'находит дурно пахнущую, но вполне съедобную тушу выбросившегося на берег кита' def move(self) -> str: return 'неуклюже ползет вдоль береговой линии' def dream(self) -> str: return 'на мгновение останавливается, замечтавшись об одной знакомой самке' class InWaterState(State): def eat(self) -> str: return 'не может есть в воде' def find_food(self) -> str: return 'вспахивает бивнями морское дно, вылавливая моллюсков своими вибриссами' def move(self) -> str: return 'грациозно рассекает волны мирового океана' def dream(self) -> str: return 'не спит и не мечтает в воде - это слишком сложно' class Walrus: def __init__(self, state: State) -> None: self._state = state def change_state(self, state: State) -> None: self._state = state def eat(self) -> None: self._execute('eat') def find_food(self) -> None: self._execute('find_food') def move(self) -> None: self._execute('move') def dream(self) -> None: self._execute('dream') def _execute(self, operation: str) -> None: try: func = getattr(self._state, operation) print('Морж {}.'.format(func())) except AttributeError: print('Морж такого делать не умеет.') if __name__ == '__main__': sleep = SleepState() on_ground = OnGroundState() in_water = InWaterState() walrus = Walrus(on_ground) print('OUTPUT:') walrus.change_state(in_water) walrus.move() walrus.find_food() walrus.change_state(on_ground) walrus.eat() walrus.move() walrus.dream() walrus.change_state(sleep) walrus.dream() ''' OUTPUT: Морж грациозно рассекает волны мирового океана. Морж вспахивает бивнями морское дно, вылавливая моллюсков своими вибриссами. Морж вываливает на пузо добытых моллюсков и начинает неспешно их есть. Морж неуклюже ползет вдоль береговой линии. Морж на мгновение останавливается, замечтавшись об одной знакомой самке. Морж спит и видит чудный сон. '''
Пример на Javascript
[править | править код]Пример со сменой состояний из State.
// "интерфейс" 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 (из англоязычной версии статьи).
// "интерфейс" 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
[править | править код]# Абстрактный класс 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
[править | править код]Применение шаблона
Namespace 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
Тот же пример, без применения шаблона
Namespace 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
[править | править код]<?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(); }
<?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(); }
![]() | Для улучшения этой статьи желательно: |