Стан (шаблон проєктування)
Стан (англ. state) — шаблон проєктування (належить до шаблонів поведінки), що реалізує скінченний автомат в обʼєктно-орієнтованому програмуванні.
Він реалізується шляхом створення для кожного стану скінченного автомата класу-спадкоємця інтерфейсу (або абстрактного класу) та дозволяє об'єктові варіювати свою поведінку залежно від внутрішнього стану.
Призначення
Дозволяє об'єктові варіювати свою поведінку залежно від внутрішнього стану.
Застосовність
Слід використовувати шаблон Стан у випадках, коли:
- поведінка об'єкта залежить від його стану та повинна змінюватись під час виконання програми;
- у коді операцій зустрічаються складні умовні оператори, у котрих вибір гілки залежить від стану. Зазвичай у такому разі стан представлено константами, що перелічуються. До того ж часто одна й та ж структура умовного оператору повторюється у декількох операціях. Шаблон Стан пропонує замінити кожну гілку окремим класом. Це дозволить трактувати стан об'єкта як самостійний об'єкт, котрий може змінитися незалежно від інших.
Структура
- Context — контекст:
- визначає інтерфейс, що є корисним для клієнтів;
- зберігає екземпляр підкласу ConcreteState, котрим визначається поточний стан;
- State — стан:
- визначає інтерфейс для інкапсуляції поведінки, асоційованої з конкретним станом контексту Context;
- Підкласи ConcreteState — конкретні стани:
- кожний підклас реалізує поведінку, асоційовану з деяким станом контексту Context.
Відносини
- клас Context делегує залежні від стану запити до поточного об'єкта ConcreteState;
- контекст може передати себе як аргумент об'єкта State, котрий буде обробляти запит. Це надає можливість об'єкта-стану при необхідності отримати доступ до контексту;
- Context — це головний інтерфейс для клієнтів. Клієнти можуть конфігурувати контекст об'єктами стану State. Один раз cконфігурувавши контекст, клієнти вже не повинні напряму зв'язуватися з об'єктами стану;
- або Context, або підкласи ConcreteState можуть вирішити, за яких умов та у якій послідовності відбувається зміна станів.
Переваги та недоліки
Переваги
- переваги застосування поліморфної поведінки очевидні, а також легше додавати стан для підтримки додаткової поведінки.
- поведінка об'єкта є результатом функції свого стану, і поведінка змінюється під час виконання в залежності від стану.
- покращує згуртованість, оскільки специфічні для стану особливості поведінки об'єднуються в класи ConcreteState, які розміщуються в одному місці в коді.
Недоліки
- Зростає кількість класів
Зв'язок з іншими патернами
- В стратегії користувач знає про класи стратегій і міняє їх самостійно, в стані різноманітні стани приховані від користувача, а за їх заміну відповідає сам клас
Реалізація
C++
Приклад реалізації мовою С++
#include <iostream>
#include <string>
using namespace std;
class Creature
{
private:
struct State
{
virtual string response() = 0;
};
struct Frog : public State
{
string response() { return " Ribbet!"; }
};
struct Prince : public State
{
string response() { return " Darling!"; }
};
State* state;
public:
Creature() : state(new Frog()) {}
void greet()
{
cout << state->response() << endl;
}
void kiss()
{
delete state;
state = new Prince;
}
};
void main()
{
Creature creature;
creature.greet();
creature.kiss();
creature.greet();
}
C#
Приклад реалізації мовою C#
namespace StatePattern
{
class Mario
{
abstract class HeroStateBase
{
protected readonly Mario _mario;
public HeroStateBase(Mario mario)
{
_mario = mario;
}
public abstract void HandleInput(string input);
public abstract string Show();
}
#region WalkingState
abstract class WalkingState : HeroStateBase
{
protected WalkingState(Mario mario)
: base(mario) { }
}
class StandingState : WalkingState
{
public StandingState(Mario mario)
: base(mario) { }
public override void HandleInput(string input)
{
if (input == "up")
{
_mario.ChangeState(new JumpingState(_mario));
}
if (input == "down")
{
_mario.ChangeState(new DuckingState(_mario));
}
}
public override string Show()
{
return "Standing";
}
}
class JumpingState : WalkingState
{
private int _time = 3;
public JumpingState(Mario mario)
: base(mario) { }
public override void HandleInput(string input)
{
_time--;
if (_time == 0)
{
_mario.ChangeState(new StandingState(_mario));
}
}
public override string Show()
{
return "Jumping";
}
}
class DuckingState : WalkingState
{
public DuckingState(Mario mario)
: base(mario) { }
public override void HandleInput(string input)
{
if (input != "down")
{
_mario.ChangeState(new StandingState(_mario));
}
}
public override string Show()
{
return "Ducking";
}
}
#endregion
#region AttackingState
abstract class AttackingState : HeroStateBase
{
protected AttackingState(Mario mario)
: base(mario) { }
}
class DisarmedState : AttackingState
{
public DisarmedState(Mario mario)
: base(mario) { }
public override void HandleInput(string input)
{
if (input == "mushroom")
{
_mario.ChangeState(new FireState(_mario));
}
}
public override string Show()
{
return "Disarmed";
}
}
class FireState : AttackingState
{
private int _time = 5;
public FireState(Mario mario)
: base(mario) { }
public override void HandleInput(string input)
{
_time--;
if (input == "attack")
{
System.Console.WriteLine("Throw fire ball");
}
if (_time == 0)
{
_mario.ChangeState(new DisarmedState(_mario));
}
}
public override string Show()
{
return "Fire";
}
}
#endregion
private WalkingState _walkingState;
private AttackingState _equipment;
public Mario()
{
_walkingState = new StandingState(this);
_equipment = new DisarmedState(this);
}
public void HandleInput(string input)
{
// для того щоб не "засмічувати" логіку
// багатьма умовними операторами та змінними
// винисемо її в окремі стани
_walkingState.HandleInput(input);
_equipment.HandleInput(input);
}
public string Show()
{
return _walkingState.Show() + " " + _equipment.Show();
}
private void ChangeState(WalkingState newState)
{
_walkingState = newState;
}
private void ChangeState(AttackingState newState)
{
_equipment = newState;
}
}
class Program
{
static void Main(string[] args)
{
var mario = new Mario();
while (true)
{
var input = System.Console.ReadLine();
mario.HandleInput(input);
var state = mario.Show();
System.Console.WriteLine(state);
}
}
}
}
Java
Приклад реалізації мовою Java
class Car {
private CarState carState;
public Car() {
off();
}
public void on() {
carState = new CarOn();
System.out.println("The car is on!");
}
public void off() {
carState = new CarOff();
System.out.println("The car is off!");
}
public void start() {
carState = new CarMoving();
System.out.println("The car is moving!");
}
public void openWindow() {
carState.openWindow();
}
public void openDoor() {
carState.openDoor();
}
}
abstract class CarState {
abstract public void openWindow();
abstract public void openDoor();
}
class CarOff extends CarState {
public void openWindow() {
System.out.println("Can't open the window! Switch the car on!");
}
public void openDoor() {
System.out.println("The door is being opened ...");
}
}
class CarOn extends CarState {
public void openWindow() {
System.out.println("The window is being opened ...");
}
public void openDoor() {
System.out.println("The door is being opened ...");
}
}
class CarMoving extends CarState {
public void openWindow() {
System.out.println("The window is being opened ...");
}
public void openDoor() {
System.out.println("Can't open the door while moving!");
}
}
class Main {
public static void main(String[] args) {
Car car = new Car();
car.openWindow();
car.openDoor();
car.on();
car.openWindow();
car.openDoor();
car.start();
car.openWindow();
car.openDoor();
}
}
Python
Приклад реалізації мовою Python
class BaseState(object):
def open_window(self):
raise NotImplementedError()
def open_door(self):
raise NotImplementedError()
class CarOff(BaseState):
def open_door(self):
print("Can't open the window. Switch the car on")
def open_window(self):
print("The door is being opened...")
class CarOn(BaseState):
def open_window(self):
print("The window is being opened...")
def open_door(self):
print("The door is being opened...")
class CarMoving(BaseState):
def open_window(self):
print("The window is being opened")
def open_door(self):
print("Can't open the door while moving...")
class Car(object):
_state = None
def __init__(self):
self._state = CarOff()
def off(self):
self._state = CarOff()
print("Car is off")
def on(self):
self._state = CarOn()
print("Car is on")
def start(self):
self._state = CarMoving()
print("Car is moving...")
def open_window(self):
self._state.open_window()
def open_door(self):
self._state.open_door()
if __name__ == '__main__':
car = Car()
car.open_window()
car.open_door()
car.on()
car.open_window()
car.open_door()
car.start()
car.open_door()
car.open_window()
TypeScript
Приклад реалізації мовою TypeScript
class StateMachine<TState, TCommand>
{
private currentState: TState;
private states: Map<TState, State<TState, TCommand>>;
constructor(currentState: TState)
{
this.currentState = currentState;
this.states = new Map<TState, State<TState, TCommand>>();
}
public addState(state: TState): State<TState, TCommand>
{
const newState = new State<TState, TCommand>(state);
this.states.set(state, newState);
return this.states.get(state) as State<TState, TCommand>;
}
public handle(command: TCommand): void
{
const state = this.states.get(this.currentState) as State<TState, TCommand>;
const newState = state.handle(command);
this.currentState = newState;
}
public getCurrentState(): TState
{
return this.currentState;
}
}
class State<TState, TCommand>
{
private state: TState;
private transitionMap: Map<TCommand, TState>;
constructor(state: TState)
{
this.state = state;
this.transitionMap = new Map<TCommand, TState>();
}
public configureTransition(command: TCommand, newState: TState): State<TState, TCommand>
{
this.transitionMap.set(command, newState);
return this;
}
public handle(command: TCommand): TState
{
return this.transitionMap.get(command) as TState;
}
}
enum TvState
{
On = "TvOn",
Off = "TvOff",
}
enum TvCommand
{
TurnOn = "TurnOn Command",
TurnOff= "TurnOff Command",
}
// використання
const tvState = new StateMachine<TvState, TvCommand>(TvState.Off);
tvState
.addState(TvState.Off)
.configureTransition(TvCommand.TurnOn, TvState.On);
tvState
.addState(TvState.On)
.configureTransition(TvCommand.TurnOff, TvState.Off);
console.log(`Current state = ${tvState.getCurrentState()}`);
tvState.handle(TvCommand.TurnOn);
console.log(`Current state = ${tvState.getCurrentState()}`);
tvState.handle(TvCommand.TurnOff);
console.log(`Current state = ${tvState.getCurrentState()}`);
Джерела
Література
Алан Шаллоуей, Джеймс Р. Тротт. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М. : «Вильямс», 2002. — 288 с. — ISBN 0-201-71594-5.
This article is issued from Wikipedia. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.