Состояние (шаблон проектирования)
Состояние (англ. State) — поведенческий шаблон проектирования. Используется в тех случаях, когда во время выполнения программы объект должен менять своё поведение в зависимости от своего состояния.
Состояние | |
---|---|
State | |
Тип | поведенческий |
Описан в Design Patterns | Да |
Паттерн состоит из 3 блоков:
Widget — класс, объекты которого должны менять своё поведение в зависимости от состояния.
IState — интерфейс, который должен реализовать каждое из конкретных состояний. Через этот интерфейс объект Widget взаимодействует с состоянием, делегируя ему вызовы методов. Интерфейс должен содержать средства для обратной связи с объектом, поведение которого нужно изменить. Для этого используется событие (паттерн Publisher — Subscriber). Это необходимо для того, чтобы в процессе выполнения программы заменять объект состояния при появлении событий. Возможны случаи, когда сам Widget периодически опрашивает объект состояния на наличие перехода.
StateA … StateZ — классы конкретных состояний. Должны содержать информацию о том, при каких условиях и в какие состояния может переходить объект из текущего состояния. Например, из StateA объект может переходить в состояние StateB и StateC, а из StateB — обратно в StateA и так далее. Объект одного из них должен содержать Widget при создании.
Применение данного паттерна может быть затруднено, если состояния должны обмениваться данными, или одно состояние настраивает свойства другого. В этом случае понадобится глобальный объект, что не очень хорошее архитектурное решение.
Примеры
Пример на С++
#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 << "Chaging 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();
}